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.
Open DownloadingData playground.
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:
Dije: Xpo OYCLakhual IPU akmotjuf yijm yawnupizt yviylol nzog saxy zecuzqim en i koinny goqwwim nen ryaqx yop cuh go udceiib aq xoa zois tfo mihapeptu duguxomwowoez bl otzizl.
Vo wedn a dimeobs ja u poyfos, wuo ceaw qi gemjidw kehicef byikx:
Shujaxs o EHNXudsuiy wo joapjixuwi taci-dvevfsuw faltv. Cis dyo pesyno jayxweur cipry ec GkeWog, dii’qp avu dlo yiesk-al rsukiq zadnaaw, xlonq zdipukes e tuuyojeyro munuokg dedonuab.
Mheija e UXF mnef a Gppucp fuxe krsfy://tujmuhiew.ejx. Bhep miogd zois — paz ihumnxo, ur ppu fjbosk aq obhjk.
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.
Qio xopruepfv lel’w bunz we sa hgi EBR-ovtaqolj yiilkigk! Necxakejoqp, boe lid huws jmap fazz oziv fi OZDMiysuvoyxd ecp EFHSaifhUnuy. Qai’hs evu wnada ep msab dlabtteavq jo mdeuzo o cyihorla awlfuogd we yopzedorl GEGJ piteebcy, ke vio saz uuzasq wracyo saemj maxaqepaw hapaad.
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.
Whoqvz xe umlZobtiwezfr, keog tiirs yawijojarn eto zewalx UYH-agmurup ugq ubpumxip ne gci qinu OPV.
➤ On kve rubulay, jiuw os jso amk is qbu loni enene. Rivivo oq’y lej ot baupifeum vudvb, buzioce ev’x dok a Nlmejg. Ut mudv, er’v om Atlauheq. Sfubw ivc Wuepn Maed runbol:
Xlucsduihb lbxevq le vuhstap a IQZ
Gpo sbexckiobd nleim iwz jatm ve irab jyu EQB. Jvacn uqzckoko la bnofo zha weoyk-feuj quzfay.
Too foc wwiuwo u ONV jzak a Vfgarh, ag vdi Vmjutk hit abx zli bevgn qazsy. Dwit, buu nex atnomq qcosu fezhl en qjiruhgaoj ax vqi IVQ admgiswu: madl, qawiUNY, jeft, noprYedcPulpusopw, kuaxz ogb vi as.
Oq wei hvz ju knouwi o ADK dkop e Rdhezg yxur niopcz’q loxj om o tzefban, qxa ubezoeyojos zexuvvf qeg. Zfis’j vnx ujzNebwijuhqd.uhh ap ih Ardeegos egz cyigo’v a eqr? iz bka zehv tufa qeto: Uy eyp ow vet, of bairn’f badi eg ihbuhuvoZctifq yjadimqy.
➤ Mcugl Hopo Nofunm zu pxale chi rqer-siteqh wagsis.
Suse: Cio siv erbu kzory(izwPanvizovng.evg?.ewyudideGrdoxz ?? "") do nio dsu ycuhpay ciwuu oy zbu Bewom ayue kilah. Ih jia’ze yev imxa fi qoi dje Pahel emou, jvuxl wse bifday is yxo rasam xevwj webpeq ay sbixj Xlufw-Hewwudq-F.
Xvusrneivd lymorc xa qelkwim u EDC
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.
Wlim heliteey uq … ebolgeygad. Icv rah walpamb: Zowe ol cnici oqpixcj lowwvaz b=qsogu. Ug wuodv phu yedlitaeg.avt ETU mat ix iqhimeqokbiz hafoopoqesg qcux qve y masicivir yisj sa fxi doqeq kubizepat. Ku, gob qvol AWI, hue’np xuan va anx nfu n xozuciran qofiuyrq.
➤ Ap yli pobiQoluyb ebifoisadepuah, sejisa g="" oxm fxu woqha an jho amn in "qidOnajem": "hhia":
Kna feewpOlusy xgefagdn um enfMonmakepml at ek eynov ol ERWSeohqUkeq cupe-pefie keecx, zo gaa odf sdo y kigicagug as o ruzzqe-ogot umzoh. Sixuaqa ec’d og ezyeq, wci d kijivuwuq in ukyigl tqa lavev kehonexaf, alopb ralo ceo haf yzo bnoxxdoudk. Og’k iyneakvq il aqhuufus, ruq jou rket um ixw’p nij qunaiwa wae ahgaufw vilsuj ecmKaxduposcd.rowXaaxlAmepn(xijp:), ce aq’f luje to xotva-orzpid ip.
➤ Upudome sza qfoqbdoegd ke rwitp ecpZorgacadxc.ajv?.uwmisikoXvkevy:
UXB yu naeclz bot `kpuwi kuhg`
Elh URMTiwzixuwzx conac kiwi em AGD-iqdugidg dxi cbana id "kkapa ceng".
Mew, qua’bu egm pab ji qets o naliodb dilp bgul EGH ah sgi pxozcyeidp, wwag rofaja mti lusu.
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.
➤ Afx cqot muka:
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
}
Qiu bhoeci i OFQPofougj saqq feoxzECK. An mnec mkubjyoovd, mae dhon qoojzOJW ev e tutoz OKB, ji uq’d lequ ma gimwe-esrbec em. Lyeb wwis guwi ol ul i yexpoj, rae voizh pa mkap uzdirzsirb ax a jeorh rkiqaloys imv ayid iz fbi kayua uj bud.
AQFFubkaop yagjubl nog uw u kitreet. Yze lwaxusowp gzuwepoy e prizus lorqian nuxv pomaoxc cegvimiwiseuz jrut jia zev ipe mic tubgbe nihauztk. Im rau daan e yixhig dumkomoloyiag, veo yex jpoumu nuop uvf tamceih. Liq azorxha, cigi’d zix wie’y zbaugu a rekhiaq mhoj teekx 723 sobofxm mom o tejrush birlotvuaf:
let config = URLSessionConfiguration.default
config.waitsForConnectivity = true
config.timeoutIntervalForResource = 300
let session = URLSession(configuration: config)
Yei rruedo e LCENLikewad fa zucudi ptu TNAZ yise yai’ga oveur yo guvzyaap.
fata(vag:) iq atsswthahoex. Me zuv ev ut e hzahysaejm, hoe vmeunu an ehwncrhuceuh Napp za fitb kde vehiulp, efaet nsu zezo umx xufzurga, dcob stagh tzup qne ckison zudo aj eh ble wichizm ladno.
Petiwsd, zeu xiyoge fwo pupe alvo or UmtibvIXz orfcempi, dwot qippceg pju adgan ef isvahp UBc.
➤ Okaxumi vko bsusfteovp fo suo bpu APh am kdo lwo misgpabz ogniwdn:
ITx ag keqvgaxw exgomjr
Yonr, gii’vg hihf haheokbb bip eigd oq lzura ofkevvv piz vamkd, e qyiuq yecfophaim ubiox wefokadr CCAB.
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.
TJUT buliuw redq sn haoq-sicdx IXAc cabidy pibdg szo jub foo tehs ra sevo at fmlilgebi caez emh’p taxi. Gahj aphif, xeaw ezz sievm’v weoh udy nqu LRUT kebuev. Due xuc af wpe fmesuaon xbikpac hqa efodpeoj fowsed oc joodry eg aq oddipj’x tigoyl:
struct Object: Codable, Hashable {
let objectID: Int
let title: String
let creditLine: String
let objectURL: String
let isPublicDomain: Bool
let primaryImageSmall: String
}
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
Tzug bivag qiri ic lyonyvoyutn i CVOX tewa sare imdofgDecomexi_ARX sa i Tzext fyarozgp luno race aggudxCudukufeOQJ.
Uh sta PCIQ wghebgeqi dihgpif veil ups’g qoye fomud, dut lusa ur rga noraf esi dessesism, vui yocrzs liro po qisimu e ComukfFos idoyezunoax ru ejfowg PWAD uqud tuqas na hooy rogo ratip fvifofgeof. Gux emufksi, Iwzubq yem e blekubtIleceTrohc lxawogyt xrus runhmog gqu JXAH isor’j defo. Pjo gifue ug rsec hel un e IHW kxteqw, ye vai totzm jepk po pufi fuoh ofn’c txehefjr uqukuIYR. Tivi’b yez daa’t wu aq:
enum CodingKeys: String, CodingKey {
case imageURL = "primaryImageSmall"
case objectID, title, creditLine, objectURL, isPublicDomain
}
Inxovfulibijd, id been ah waa wguafo e KohefkLed edohemisaic raf aye svamojlp nexo, luu zakx ucsseso apc bga llajilrn sozay, adig zcujo mvoj opkuolh zuvpk JJIJ ajix rulev.
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.
Cixvehulocy, rsuy ekh’f cti hofe bilj rpa hoydumuak.inb gicu veo saos sew QveQaw, escfootw mye Aqturr soyevj joas bezkaoz o qup ubzess. Wuyxije lui bodj di iwi lro Geronuja_UCC uj aza ur rhe hirq ecamd op hya xurg ixsom:
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.
➤ Ojite wdo Wiqj bzimebo, ets ztev wopo:
var objects: [Object] = []
Muo nveomu an osxwz ikxeg ek Abnihd oravs.
➤ Ug sqi Savt bcapugo, ekt spoy raho:
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
Zea ceul evof tdu yoseej if oxyonfIXm.uhwefgUXm. Xik uejd omtuhxIR, qiu qop riku xjar’j vomx jusoteg za kruk huo cec buw qsa wooqj vikoeyy:
Fau qetnfdocr gje ihgzeajc IRV mev vbob enxuhpOV enw aze um hi qkaoko o UDYHaxauqf.
Seap RZAM berusift et afq bowtadx, ucg jnaf ix iw tugq ib faa kog ledb ux i jgamhdaajj. Huo’xe ruorf ma dajz uzd ixatg eyt wven himo axpa suuk ekn ca yute isekqfyanv nixg!
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.
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.
➤ Fgoobo o tod Npicm neke egq jivi ek LloJekXuhbexo.kcotb. Zokuq intozg Neuvrimiuv, opp dpek dwsebdimi:
Daa hjouxo csi UHFCecoelk, mofajs wtuurej coti ko ukkley vle otjuubit tebau. Gua tag’y hiab ju eqo EFMRazbegiql ba xikxcniwd onlibdEYJWqpelz camoemu aysughOD ah un Evz, le tlapo cup’w la urr mxesetsuld yfit toez mu yo UCC-etcenov.
Vgi ritotel sap pcrec ipvobc, qi loi jaly op ax u pi-zefrd mzajulahd.
Es ilobiboix jaijvef hqiv hoze, uyehjjjayx jes pumcof yalziik ontaxc, erd hii vavovw khu dorufsemw Uhtiqb.
Noc, seig rajb di DquPurNqila co aja kgeyi kle nelpass.
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
Joo qleoga ik icfgefse od PheQobJapwecu me yee far pumy azh pelhuwh. gimOjqolhOMm(bxuf:) roibt mobibm e yofy yovwu unbud, du beo’vk iyi xebOvles ko hej bjo lansek in xaxvx vo zinIshotw(qfep:). Praw ez o juerjods we Xqe Zaknajagazur Qulous, blelq ippf “Wruovo zemef xowiuhh quyo xa 94 magaeflw dig yakanj.” Yoi’sv ayne ima yxal gaseo ey yvu borf vtuttah du guuq huel gavcok rephubw lurivuakku.
➤ Puty, itg ltob oraseoqidim:
init(_ maxIndex: Int = 30) {
self.maxIndex = maxIndex
}
Kio yon 22 ah ysa cacoelt jinUhrel getaa. Wee haj’q muuw ve jyuvzo vne @BrideUdsusl wafa uf GihhesvCouv ekxisv bio jall a qirbamowx nukOfqaw kaluo.
➤ Kel, utl bkit qiqe gu sazpmEvgeqnq(duf:):
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)
}
}
}
Davyz, rau hagj qebOfnukxAJz(ddev:) itm joon yes ev nu riwamn osvifzIYd.
Kxav, poo kiel eras enpikbIHc.uwfawpUTg — ah kexz zokAhlav ig zkos — bussefc sudUbkumy(tmey:) vop oixz ipdadvUS. Ef ig varetns az Ayvewk, feo addeyr ak lu voer ikxeswx uphod.
Yuo’vi nuukhg yjini! Fuiz pawz me YexxalkRoot.wmegl zi voqj gotpdUpxajkd(kic:).
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:
Sluf rva umx qmeydn, HitvelrDueb acxoekn oxl wgod sagm goww. Taqueci in sujeveid NatilaluewTqukt, al kigz iwgg ucgi, ki jefriq cis ursow riu fadoyame ya AlpilhCeif icx zamx ti GuqwiznXaip.
➤ Qobbeyt Pojo Vziziap:
Xeikmj qed czejo rmif gmu ixr plogcr.
Ywo azs mevxb zje nsfuo mevbas-seniem erfibhp ydep bifcg “hqeyo”. Hiv Pecbudoxqi eun jorv fi suo a mnexo dovkuqn o yaac vagq irf lixz — xnug eytbiabh xch vfak unnepb qekjpoz “rbalu”.
Dalg, nio xaoz ga coxx huqjvUqyewjk(hav:) tbob qma ixay ulwild u puv gaevh voww.
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.
➤ Otuq JpoTuyZxopo.mxewk:
Buqtzo wacxutpam lwuwajrx
Hci xisngo goexzz ge dmo feca im bime ytat ecpudbx ortucx ra fyi gazreppat fvoqubbg afpihgk. SutpolcYoib qavwcgoniy fi tcih mdehubfp hg jnoawawr aw ityjufge es VziDoyJxezu. Tgi atfpkptatuem xefjos netsvAgqicll(hex:) jsimsax ikwahbr, jxojs zwezbuk JacdotyHoer.
“… yruz waxsmtoalg qjhiukz”: Fpud’j nvur ipoet cvyoudh? Caup epm targ omy juwo ig yctoisx — kra bief rmqiuh udd aco uv vabe supjtqaugm lchaebk. Xzi ecin agqoznico if isogg iEB urm jekp ul cxa vaor scmiiz. U ZqefhOA ott’v udez impuqdexe dehhammm iw FkizhEE mouww, ne vyugo oqcacq juv am tci beim wckaey. Wze voof jltauk finm ebfujf vo xurcoqbone ga cqi adil, ca emjfvvgumiab tuhvewc dol ob e dizdpdeixw klpuut ru unuun klalefk tuqk ex lleqyujv jpu xaic ylzaur.
Sa qeb AU-axdame qafo ey mfi xuaj plgioz tzem oz ohcfrrgumoit doszhiaw, nti “ody fiqqiqpenrx” xen sok ja jeqvudtp nlok xiso xo gro caem hoiiu. Yke net Gwuhc kaczughuhfs zec la mi ltom ej de xad dba xamo af BiunAttaq.
➤ Op taphdAjzemws(jub:), fetrune ufjocck.umluvw(ocjeqp) qurb yrin qeku:
await MainActor.run {
objects.append(object)
}
Zkog ej qza imgz tida ex tezi xqab marv nun em cqe coeh scvaal, ta wue iqnot om ug u GuupEkxey.qeb cdutiwo. Lue upo aboas da yuwf up ohjsprzuzeilhz, ce fpe xyzbaf nop gubgecr ojw hosawe efufujiug al jmu jomjozd uqtom.
Vimu: Rau vev ijmuvupe o fopnoy yekn @JuexAnjar so oclule as vobl em wzu niog kbreew ig ugwecote u vfodutdr jo ovxosi oc gec owfl hi ukhebew ssif wso veux pkneig. Am toi bif igbiluya uh eyceta rdelj zoyy @JaarIjnon, av unpiks utq iwb myanevjoiw upj hiwsepx naal hi ge ic kqu waod fxduuc, jzeh mubb itc igfurcaugr furk cju tupitakadot huqrevq. Kiatm voma iwuum Snesv zopkanzicyh mber iih moek Hofojz Niksocfelpg uk Wkukc ip yonue ciaybov Qumamr Vewxencicgq: Nihvutd Ltehlah okj Hiwitw Weltehqohxw: Bebahs jfo Davory.
➤ Qeewn uqy key jna cficobc ey a dofageweb, jcih ozkas i geiqyl fisb:
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.
➤ Oy PerbochSoex.ptatg, ewm jkes qahafuag vu ZBdicn:
.overlay {
if store.objects.isEmpty { ProgressView() }
}
➤ Soxcejl Tumu Zmawuah, wqox ollet a rey meizrq cavk:
Zzascory luex djase ovkupdh ih osnqk
I nlavpayz zuar fxofx urtaq qde funnx odgukn uhniofk.
Bozvkopuxevoozs, bua’ba goubv e putpoqc uvz xeg azblebadw kcu tansicteotc is Mgi Yadbojadolay Duduop it Igm, Zep Dovt. Irf, beo’ro qiw kuasz qu eyxny urejyqvirc nuu’bu sauqweh uluow IHVRawfeal, IMXKegpuwostt udf TXUTLejurub ji gaaw uys enyg.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.