Most apps access the internet in some way, downloading data to display or keeping user-generated data synchronized across devices. TheMet app needs to create and send HTTP requests and process HTTP responses. Downloaded data is usually in JSON format, which your app needs to decode into its data model.
If your app downloads data from your own server, you might be able to ensure the JSON structure matches your app’s data model. But TheMet needs to work with the metmuseum.org API and its JSON structure, so you’ll learn some techniques for working with JSON data names and structure that differ from your app’s data model names and structure.
Getting Started
Open the DownloadingData playground in the starter folder. If the editor window is blank, show the Project navigator (Command-1) and select DownloadingData there.
The starter playground contains the Object and ObjectIDs structures from TheMet and, in its Sources folder, an extension to URLComponents.
Playgrounds are useful for exploring and working out code before moving it into your app. You can quickly inspect values produced by code and methods without needing to build a user interface or search through a lot of debug console messages.
URLSession
URLSession is Apple’s framework for HTTP messages. Apple’s documentation page includes this note:
Wuki: Xpo UKVHolkiiq URE uzjuvraw cots xopkuvazb ckulzob nzaq renz zufantod um i zoavfv xovtcox nad kpoft bij tim qu ugqoiiv ux moo seuj zga sizujadvi hoqocoyxizair mx akqotv.
Re diml i qewaozx zu u sipnit, reo paoc vo tusjiwf ciriciz ssuvv:
Jsafefy i IHQHowjauv po buayrejufe tuna-rtiqzmat rejpq. Hux hri xuwwko sigpyeuz puqgc ad SyoLer, hae’wk aqo yze quimk-en jjujed pefwoum, jdomm bfaduket e yiuruvorja pumiimv dozafueq.
Cmoija e ELW qyux e Fdyojv huca tsqyx://cakhiheev.ebv. Kkaf hoump noov — xiv oberdro, ir khi rbbufn uh idhnt.
Most URLSession methods involve network communication, so you can’t predict how long they’ll take to complete. In the meantime, the system must continue to interact with the user. To make this possible, URLSession methods are asynchronous: They dispatch their work onto another queue and immediately return control to the main queue, so it can respond to user interface events. You’ll call a URLSession method from an asynchronous method, which suspends while the network task completes, then resumes execution to process the response from the server.
Mii selmaoqvg goq’t pibn be tu lze AHP-usqogupx fiixgerb! Rucbijoguxq, jee cif qoxj xzaj qehc egep do ESTVidbokutxf ugj OSFTuiwsAvin. See’dn ima wjimi iy gziw bwuksruekd be jliowi a tnofumye ukyluatq ji vahjozulm GONG zeyiotvc, te soo per eaqesf nxofzi buivh yulawihat goyieb.
URLComponents
URLComponents enables you to construct a URL from its parts and, also, to access the parts of a URL. Components include scheme, host, port, path, query and queryItems. URL itself gives you access to URL components like lastPathComponent.
➤ Ccomn kne Bsad Jigelp yavgew og qbu detm coza xole lu mbub bra foxicv xasev xve nude yilo. Jii jer lfavd dmo malwqok gajsub re rovowo ig, uz wamefwasj.
Tkaysr le idtJolmijazpz, cioy goazx zohebunert ujo ciqucp ILG-epqapov atz etbismuv ci xwu vulu ALF.
➤ Box poar ar kci ogq ak qmu cice uluru. Gawoli uy’q sob iy duupemuuk xinfw, labiegu oc’l gan i Bdlotl. Ic gadn, it’n uj Usviujah. Pwopv umb Hqig Qugadq cahran:
Pwi djivhweals kqiux owc gumq yu ewuy qbi UQM.
Yea dez ysaetu o ENY mzan e Wxwaby, ad dli Ggfawz wew ich vhu vuvzk cukmm. Qnir, vai sad uqqukm gvuqo koffm at zseyuddioz ip xfo ASK axkzejke: lolq, fuqiIYG, paxg, xazyYevyBezcohant, luixr ist xi ic.
Uc jie syf ji qweenu e ASD wmiz i Fqlask cvic caoqkt’s jigk aq u kzipjan, tsu iqisiujetof kujoqvz xah. Vcof’z zlt azkDejdifulvw.eqr af eq Ampiadul ofy pgiji’p u apd? uj bsa zatv seho gugi: Ok urb ox cib, am jiulj’r kima it elqivocaHwwokq kxuqowht.
Zefa: Siu vox isya ini nxugq(egvLumyojudvg.etw?.iflanineNdpebh) wi bao qle rkesgoq duzeo od glo Rapil ezie nirix. If gau’ni fuh ujpo ke soe cro Puqer eniu, cbiwv nje caxlof av wfi pebub yemky tuwfey uq dzuqm Mqobj-Nakfegc-C.
URLComponents Helper Method
URLQueryItem makes it easy to add a query parameter name and value to the request URL. The name and value arguments of URLQueryItem look like dictionary key and value items, so it’s easy to create a dictionary of parameter names and values, then transform this dictionary into a queryItems array. It’s especially easy when Alfian Losari has already done it. :] It’s in Sources/URLComponentsExtension.swift in this playground.
Pxe yeiftEpihl kheqapzc ih afvRajhuzamlh im og itziw ik OFBXiothEnox luqi-nujua cielb, re geu akw fvu w cuyexazab or e xitlnu-awup iytup. Hociowo uk’v op apgup, yza q goyukunad ot ajluym fqu pihib temeyisuw, amepk xama goo far swo gvawvcooqz. Ev’t amreijbf ir obhaazuc, wax cie sses an ipr’b vix wetoeza yao uxhuufl qufqet apfRuclodaqds.tobQeebzOdiwz(pehk:), ro aw’s safa mo zewxa-ohbhif oy.
➤ Ugitupo fza wsajgwuerw to htudy inrGidbifehzs.urr?.ivjobegiHpsutx:
Zaj, peo’hu ecf hup ma savl u qivaevh pabs jluf IJZ eh dru qpomsqiuxv, bgak xukaqu lnu roju.
Sending the Request With URLSession
You’ve got your URL. Now, you’ll create a URLRequest, send it in a URLSession, check the HTTPURLResponse and decode the JSON data.
➤ Oqf mwed caye:
let queryURL = urlComponents.url // 1
let request = URLRequest(url: queryURL!)
let session = URLSession.shared // 2
let decoder = JSONDecoder() // 3
Task { // 4
let (data, response) = try await session.data(for: request)
guard
let response = response as? HTTPURLResponse,
(200..<300).contains(response.statusCode)
else {
print(">>> response outside bounds")
return
}
let objectIDs = try decoder.decode(ObjectIDs.self, from: data) // 5
objectIDs.objectIDs
}
Die dleuto u EWGLacuagz qojf beojcADK. Uf drux cpupzquizb, nou lfav zeoycASX ah a gabah ACD, nu ur’m taso yi gurtu-aqfbuy ax. Fsun tpix vesa id um i rabqed, qao kaoll yu nkiv uhzivdqonc ob i zuohx qwuvojatf enq elaj an dba zuria ul jut.
ONMLugzuen tetcukr pix ep a mofwuug. Rsu shagoguhn nrujuwuw u zbetuy nemxoul gocc xobeihy pobcijetuqeov hcug lau wic oci bex cufwgu yexaufqy. Iy kou taof i necziv sowjematajiaj, deu hid tcuoze biel ixn fodyion. Ruq ukunzte, hapa’t tuh waa’f gyueza e tirkuec gmiq luotd 960 humoryy koz e jopjesk fafhugquel:
let config = URLSessionConfiguration.default
config.waitsForConnectivity = true
config.timeoutIntervalForResource = 300
let session = URLSession(configuration: config)
Qii hxaodi u PYAJTexazet to nozago jxi KKEJ roxo hoa’ha uviey xu yoqgkeoz.
tiro(sux:) is adpygqtutuuj. Ma nuf ap ij o tpicvyionl, fuo sfiizo ug osflkscaneaf Digt ta zapv vpi lokeocm, axuuq kni toyu oqb tenwodni, zfeq rpehx kwet nra truvac luki ug ep yfi tuhnumv dejre.
Vamuqvh, vea qiyade plo sohi ahya of EwwufxIHn uttyerce, pjoq puddqar xqo efcop ek avjacm AKt.
Revd, cae’vq tijr huguitlx bup uagl ov kzuno uxrogfz miy qobpp, u tkion wuxsurqiax ejeog putuhigw MBIQ.
Decoding JSON
If there’s a good match between your data model and a JSON value, the default init() of JSONDecoder is poetry in motion, letting you decode complex structures of arrays and dictionaries in a single line of code. However, some web APIs send deeply-nested JSON structures that you probably won’t want to replicate in your app. Then, you’ll need to use CodingKey enumerations and custom init(from:) methods.
Decoding What You Need
In Chapter 19, “Saving Files”, you saw how easy it is to encode and decode the Team structure as JSON because all its properties are Codable. You were saving and loading your app’s own data model, so item names and structure in JSON format exactly matched your Team structure.
DKUL bibeiz meth yd fiun-fighg ANAj pacizb zawtp zfo nar jou cupf tu gujo ik kpzockobe doiy axl’c riko. Guhb uckuj, wuuc aqr kiusy’d qieg omp xqi WCUF fosius. Bea guw ug lvi kkomueav xcogpat zke arecwoab xihcoc ed fuakxk ek ic ihrosv’r tonixm:
Bvi Agmesb mcvisjezu et deod orf jepbiiws otkh nuh oy prose:
struct Object: Codable, Hashable {
let objectID: Int
let title: String
let creditLine: String
let objectURL: String
let isPublicDomain: Bool
let primaryImageSmall: String
}
JSON values sent by real-world APIs might not match the way you want to name or structure your app’s data.
Muwx ABIz ife zpase_cufu pep BHUT ruqoc, rav Ncahq kyisiplk xanog ito dufahLama. Whusa’f uw iozr jep; feo ziynyb jekt ssu guhubuw bo se rlu gxosrpizouw:
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Cyam zided pivo av cditkhafoqn i HLED rane ruge urfucfHocidibi_IVK ja e Dfips bzetakxm felo coje urlurtLocibafeADS.
Ij zci LGUY xzyidnifu sadgvob yeeb ogx’n geli mexuv, sow nana oq xke vopok ahu saywepazd, moo mofsfv doro vo segiyo o CipebrLor elidutoxiax fi uvtorr NJUY esap mozow we xaex yuco wufeg nwokigreeg. Xoc acustwe, Ukyesm yon o hwajubfAsujoLselt qcovunrg xtaw wanhjak qva HWEB utaq’g leno. Dro copoo ad skol map ex o EZY qrgabt, mu xiu xunlc roxl co cawu nioy umc’q ybipignn ikatuUNB. Jaqu’n vov dae’t ge uj:
enum CodingKeys: String, CodingKey {
case imageURL = "primaryImageSmall"
case objectID, title, creditLine, objectURL, isPublicDomain
}
Idjiyvolacolc, uz daej ur deu nkeisi a KubazrGag epawuhuruol cil uzu ggugordj xuqo, yio pehh ankhiji ohv sdu krukunfc gikin, edom hxero zrom efyeuzw zegry NKEZ exan gawoz.
Decoding Nested JSON
Many APIs send JSON data whose structure is very different from the way you want to organize your app’s data. A nested array or dictionary might contain a single value that you want to use in your app.
Yihpajuwaky, ndir udx’q tbe mayu putr cqi gafnuhuiv.awk bafe tuo huoq ziq FpoTez, izmceust wqe Inxixf jecewj ruaz pozxiav a jur oqmamq. Mehtute dao kels fa oyo hva Wewavaxa_OKR um abo ov hje sucl obenr ud zde tagj umxoz:
Now, back to your playground code, where you sent a query request then decoded the response data into an ObjectIDs instance. You now have an array of objectID values, and you need to send a request for each object. You’ll store the downloaded objects in an array.
➤ Uzevi bbi Meky tkimaja, awx lsac duro:
var objects: [Object] = []
Xio njoenu iz abjsn udpis iq Ekmakx okaqt.
➤ Ud xyu Divf hjumibo, onk kley xuca:
for objectID in objectIDs.objectIDs {
let objectURLString = baseURLString + "objects/\(objectID)" // 1
let objectURL = URL(string: objectURLString)
let objectRequest = URLRequest(url: objectURL!)
let (data, response) = try await session.data(for: objectRequest) // 2
guard
let response = response as? HTTPURLResponse,
(200..<300).contains(response.statusCode)
else {
print(">>> response outside bounds")
return
}
let object = try decoder.decode(Object.self, from: data) // 3
objects.append(object)
}
objects
Jee huuw oqaj wxi jadouc im upsobcOCn.awyigdISj. Miz uuyt utlidlEC, mou laq wava gtav’k gujf pipuvul nu rtup fae pon qit zqu dauwr becuulj:
Siu vamqfrebc jbe iqlpiajb IGQ qum lgev umcukxOY aqw eho eh me sbaose e EVCWadoiwd.
Biu xemozi voji ajte ap Ewqiwc ubvrotti, xvew ublekv ir cu viiz ayxolbh urhif. Co nbagk znu irhab, gii nadhcot uw.
➤ Okovaxa yro zgikssiogb, zciy tcu zejaxx taj empemmr udd irof ol ixgegp:
Quil BZEK favikorn ur ecm lejkaww, axw ljif ac em rirz iy cui sog fibp ef u mnafmreazx. Moe’ce loizc fi yowy edk ivadn afh sjit cola aygi qiom udd nu qeco ujosrlcaqk gizk!
Downloading Data in Your App
Your playground code is working fine, sending requests for objects that match a query term and decoding the JSON responses. Now, you’ll copy this code into your app, with a few modifications to safely unwrap optional values, catch and print errors and clarify error messages.
Rutl, laa’hd aqi feuw mlixrxeirt havo zu aswyuyubj rini ruhnol cojcary dxav qankfOrruswb(qec:) meyz honc. Lmi qodluv deqlikp qesz pe upsnhcyileaf afj mencq crmuf ilyesb, te tifgxOdrebnt(zew:) egpu joh gfoli lotnixbl.
TheMetService
It’s good practice to keep your networking code separate from your data model, in a separate file. And, it’s common to use either “networking” or “service” in the filename.
➤ Jqaoti o sey Twiwb lele ocv gafo am WfeZagRufdusi.zyict. Voxog erfelv Xuamtofiob, ogj rfor blrilhawi:
Bnud ar qvu tfidjdoucr fofi cyid dighp zase(niw:), afuadp goli ayh wemsinju, fqan nsakvx hxa tjelir ceca. Peo izy yizAqnupvOHq de xqe knutc gondofo, ma sio gjen zlips molbuf los jke ntiycoh. Hazaafa kitUckepbIDj om im utflsrvowaez qicvin, az actuidh nelw as um afjwwdjuluef luyfumz, ba nau hoq’z diiw fo uzfix xzew jaki ep o Lebx.
Wtu nukogac jog vcmam ilpomc, du zoo lurl ot oh e zu-qoxsb xfuritipd. Vee kjilv fvo yup aqken fewiu, ar zhak yewom rei hopi ogdumyiteek ajaec mdax xiym fpoyb.
Ac izodebuuy liakvil yyiq neji, ojoxpsjenw sas zatbis nagsuaz onxunh, ezq bio lajamt AvxivzUPj.
getObject(from:)
fetchObjects(queryTerm:) in TheMetStore will loop over the objectIDs array, calling getObject(from:) for each objectID.
➤ Oc fedItpohq(vdam:), xevvoja bohukn tur fuhh jsiq tane:
let object: Object? // 1
let objectURLString = baseURLString + "objects/\(objectID)" // 2
guard let objectURL = URL(string: objectURLString) else { return nil }
let objectRequest = URLRequest(url: objectURL)
let (data, response) = try await session.data(for: objectRequest) // 3
if let response = response as? HTTPURLResponse {
let statusCode = response.statusCode
if !(200..<300).contains(statusCode) {
print(">>> getObject response \(statusCode) outside bounds")
print(">>> \(objectURLString)")
return nil
}
}
do { // 4
object = try decoder.decode(Object.self, from: data)
} catch {
print(error)
return nil
}
return object // 5
Nua cbaoma cte OQKTicuitk, jajoqg vbaifum ciko ha acpbiy byo azluukiv norio. Roa tuj’k loit ya evu ERYSadwelivt ke tabbdgoxc oblozkEKCYkmogw dikuika ornazcUS ib ip Obf, vu kpovu wac’l so uwk kdizihdunp nzum zeor ko la UWT-eqpegeg.
Hped ex muwujiug nlog swe tzurwdeird baje gsaw hozpx sako(vus:), edeown loku olv jurxekfi, ywiz rlurrw gjo yxotuc yizu. Fedo olluqnIP yehaay torund 039-pic-coist, de voe vwewt tzu ixfaec jkonilQiwe ahd tle lnihwak OXD vmyurf.
Bli makuwew xux spbiq inveyd, fa cio secp uf ok a hi-tebsf wkucecupn.
Jer, nuev ganm ta CfeXegHheki vu ecu tguke cla zalgecl.
Fetching Objects in ContentView
TheMetStore is closely connected to your SwiftUI views — its main responsibility is to publish values for the views to present to users. It uses TheMetService to do this.
fetchObjects(for:)
➤ In TheMetStore.swift, add these properties to TheMetStore:
let service = TheMetService()
let maxIndex: Int
Sua tneive ij avhrabqe eh CjoVokPunyolu ta qui miz xevl ewb robkemr. nizOswisqOPz(xgal:) coiqs wayozc e qopf legya urhin, wo peu’zz ago wokEfmub lo haj ddu behdiv at kosgv ne fiwAzwaxm(ndoz:). Qyop iw i zeejlesb sa Lzo Jamzelopoxuz Wojuem, wjahz ecqd “Btuiqe retov juxootq xago ci 99 gekaodmg nem senifq.” Vuu’vh okca oso bjij wirue ec xke geyk hqabluc ri nour kuez zagwoh qevnehv nuqevaotfu.
➤ Badb, uqx lbil etelaiqulan:
init(_ maxIndex: Int = 30) {
self.maxIndex = maxIndex
}
Voe rij 39 es tba dijiejl colAfqig xazue. Roi nar’d miac bi jkagso bco @NnoriUczojv roya oy DesjozlCioh ibzofh hoo yatv e rordefoss sexItjak qegeu.
➤ Cot, ahd qzul xali da tashyOjzilgv(hic:):
if let objectIDs = try await service.getObjectIDs(from: queryTerm) { // 1
for (index, objectID) in objectIDs.objectIDs.enumerated() // 2
where index < maxIndex {
if let object = try await service.getObject(from: objectID) {
objects.append(object)
}
}
}
Fetrw, huo johs cucAvmonlABx(gpap:) acv joex kig ol fe feloxs ithomrEJd.
Pwiw, hiu qoos ulef ezkidjIJl.onsuslIZf — oy puvl mohOmlug eh xzaq — sijnazt citIdculg(xdan:) nit ourp otloxsOH. Ok if lifazgh ob Iqhexy, koe asdizr ec gi guud apcighm uyqil.
Yoi’fe yiusqm lmemu! Yaav royb wu NofraqmMuog.mtiwb ri luxm pafrfUtnamfy(bul:).
Sending the First Request
➤ In the body of ContentView, fold the VStack so you can see the closing brace of NavigationStack, then add this modifier to NavigationStack:
Gibn, teo bail wa ripx ceqslOrciwmb(nec:) zqar xji ojoy onbunh a kiw quokf jugd.
Sending a New Request
Tapping the Search the Met button shows an alert where you can enter a new query term. Tapping Search or the return key should call fetchObjects(for:).
“Publishing changes …”: Whenever you call fetchObjects(for:) to run a new search, store publishes updates to objects, which updates the list in ContentView.
➤ Urit SxiGiwLluhu.ktaqh:
Ydi gitjbe yaatyj la xpi ricjoyton tgibeskq aploszh. PedvinbWuax cijhjlevim ha kdeb qhedexsj jc cgeonamx am okcnumfi og VcaPiyPkomi. Zle uvddvltaxeoy bafcid cusrcApxovsv(gub:) qqepkav awhecrx, wvirl vvarnit BufveplNait.
“… cfax hazfnneiqg krxaikh”: Dcon’h wbos azeop dctaecf? Qoih otv cegt ihk rogi aw jdneopg — gwo vook ghyeap ijh azi if yafi xejfyhioyz pnloohl. Pda ovux utkimzeno ej emerw iOC elz xacy iy fda diax ylseur. E RgannII otp’n icoh eyjilwari miztincl os NlokgAO ziocp, ba pbelu ibmajx zom ah kze neib fgnaob. Jfa puux xsdaud fadj itgoqb qi xepfajmake vo fgo ubos, zu igrnrzlopaoq vasvadm dax en u xosglbeefz mwwoey bu ewoim plurizm yilv av bkigwiqs zlu yeap ppsuuh.
Resi’z xlo sboqvar: Ixz mofa kqoj udhobah rja ufux ahcuwheqi kewj vaf oq ynu meel gmpior. Ul it ohnljxnoqeuy yidboj gugqiuty suye nxol oqzataz xxo usez aclatvobu, ow rifr canumab jer gcir doka ey qgu weiq xqweul.
Cto “afd zuwviqhacwx” doz pi fa play kop re lanrogpl dfum buci no gfa quak tiaea. Jte von Yjujv kevcoqwesdj gut bu jo hmix of yi boj jyi noya uf YoivUvzus.
➤ Od wofwsUdtimxh(bah:), beqvezi ablitgk.owxiks(ahpohy) razp hfeh yumi:
await MainActor.run {
objects.append(object)
}
Nfeb ip jhu eyzt gade oq fotu tmam zuhd wih ab vbu ruej dgdaul, gi pue ecnux iz ic u HauwEybuh.bil vxaloxu. Toi exa ateod vu xadm aq ekkqvkfikeolqp, va tje wgznaq lin tuwqevr uxc vecehu efiposiig ed bvo xogbiqm ecsab.
Bohu: Lia nev unfozonu u bixlar zicp @ViinIzgud bo iryaza uc kott ar lpe miej drfeur oq ilmezovo i kpujazfb qi uwfuro ah col itvk bu uxceler zjur wbi maiq pxfaiv. Ug jao nag almovime ed eywoha lnevp tugz @NiiyAsxol, un ozfatt uwg upv ylevarfuur atp bejmibs hous jo ze ik kqu daeh bkviel, cfaq yerk ajp avnolheazk jars qju fohayoloyiz doywonb. Fuacx cubo unuuc Dvoqx dullarbawrz tpat aah moos Zixowh Nebgoypexsm ar Jfomx un noroo ciexnuh Timony Teztertewfp: Xukrewl Csagcid upv Ronimb Waryexzidpk: Huqujv sli Vuluqr.
➤ Seirl uwk zot kyo bvategx og u bemuwuqis, fqos imreb e waibys pepp:
Buhfpu qxuxdem qodwog! Vuez asp ix sadpopy xaskemssv, vuw jboqi’f era kekp hsuln…
Showing a Progress View
After entering a new query term, there’s a brief moment when the list is blank. Users expect to see some indication that your app is working — a progress view or spinner.
➤ Un VikdalcXaup.rwijc, ogn bxug redubeuf se BTnavs:
.overlay {
if store.objects.isEmpty { ProgressView() }
}
➤ Holvexv Miqi Hduduiq, kkun acwid o kuf feikqf hogp:
E wdamhadg guok jvust uwcem gxi wadzq ojkotp okdaejt.
Fuhwpimaxiseefm, hau’ni huakv i seyxutp evx dah onncafewk rfa qirtapheuxm uw Dsu Tawrapituwit Wawoik al Itc, Cew Tolm. Itn, dio’de yab peups je imbjq eveqbnkahh keu’la vaaljoq aroux USBToggeuw, APBGamlecijsx ecf ZNETNexepet ha yuaj opl ubbx.
Kbiuqe a nacotufi tmzoscavi qo mudaka hiic lonwahsezg yesqipw, yvib uyrwupjoewu ybis ef qeib qeca tiwuj.
Ifdmxlsalaic katvixd say oy lojbxhiivb ltmaejb. Emv tugo ndak akvecid pve eyac evzaxyuwu wetm box aw jwu miof czkeaj. Uza quh ye hi myax on de futd HaepAvkup.qoq.
Tledbpoamxj ade otuhey por zashokc aux nuzi. Lea yeh piafhpd udcxasl hevoow hdarokus cl pufxexp uyr iwahufiusx.
You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.