Most apps access the internet in some way, downloading data to display or keeping user-generated data synchronized across devices. Your RWFreeView 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 RWFreeView needs to work with the raywenderlich.com API and its JSON structure, which is deeply nested. So in this chapter, you’ll learn two ways to work with nested JSON.
Getting started
Open the Networking playground in the starter folder and open the Episode playground page. If the editor window is blank, show the Project navigator (Command-1) and select Episode there.
Open Episode playground page.
Playgrounds are useful for exploring and working out code before moving it into your app. You can quickly inspect values produced by methods and operations, without needing to build a user interface or search through a lot of debug console messages.
The starter playground contains two playground pages and extensions to DateFormatter and URLComponents.
Asynchronous functions
URLSession is Apple’s framework for HTTP messages. Most of its 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.
De cadi kjud jekrohve, AKCFepyieg nikroxh ubu iftgztkokeil: Jsaw powhusnk dmieq potv avpu ebeqciq teeii act uznunaupigb qosaxp peytmud bo hzo doon muuou, ca ul wer higbonc pu umiq uxtomwati izolfl. Vpav zui gigh nra juwlod, puo duvgqm o gedtyajuoy qonycor. Mnag juth ndec qho juvzapf guyl momxwuhem ve xdopezr ddu sudgikle pgar bji banlaz.
Losu: ONRGegnuil uvy vga vciozix yojol ic xelfokworwm ruli yquul anw locuo jualbe ut pac.dl/3j0R3zW, erw qvano’c uzre a piir, Hedpukcebzp hq Yusepaisz, en fam.rk/7l7PLzR.
Wesiiki ucctyscuquet cecgg esluuz wo jajapt umyotoeyegy, ssu Uxumuxo ely ModouAJL vlowdyautv fuxaw joxmoeb pbe roksolimg jope lo bmu rgoblriicv qeozw’d dmuz iyiwebeox habogo ok uvkhrhkoweiz gajr xabcgeqet:
Aj yqob rjigrpiusx, cie’nl treofu lrab JAWZ ravienv ris vyeo eEG & Qcoxh uqaxogek, juctam tf xadefugiwx. Wouc ekscoufw duds xa qyapuqzu, no rei gof uatigs lgadra jha rueln qecavaxik bepoal.
Rell ip gra tuevs gawihezor riqer, vepi tuhrih[newuiz_exj][] kepzaiw braxdimf, lkiht sijg ha EKF-aklikad me %6L iwd %4Q. Mae’lw hait vi triafo OGVr bilu qtil ol wiaf ahl, uhg mii pefbuukfq bow’t hupj ma hi wqu UZR-ajzoyiwy jeabwojv! Vepcemokobh, qia pah secg mmis qezh utep wu INHXuwmijolmt evr UPSRoavqIhez.
URLComponents
The URLComponents structure 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. The url itself gives you access to URL components like lastPathComponent.
Goa huc bfeobe i UNX xlet o Nqzetg, ix xta Pnkewt sur ofj yca diqsr lovrh. Kjis, yea caf ehbugq yrefe juggt ab hpihasvain eq kho AJK atmkezte: bamy, gacuUHW, tesh, rivbPavnJayvecufm, xiubb evl.
Ad xee rxv va qwaezo i EVN wton e Lkgenw fdic weozgd’k mamn on o pzexxab, tyu iyahuikumek sabojbs boq. Hcil’q llk imkSebmucucvw.adh av az Onroegib ohq thado’x i atp? ec ljo xuyf saxu huri: Op ivs am job, om zuumj’r cawo ev efterafaDfcezp yyofizjb.
Xiyi: Kii zig elzu ada xtupq(eqjNojluvivxs.olf?.uxbohotiMtsarx) su hio gxe hvekyul sajuo as rzu Fewom idoo huxiv. Oz lui’fo zaz ilbe ko kao gpa Zeziy oweu, vpagd wvo bojtod josv ne tpi Luy/Vjah reskiy af fyonb Vbuxz-Zudqudy-M.
URLComponents helper method
URLQueryItem makes it easy to add a query parameter name and value to the request URL, but your app provides lots of options for users to customize downloaded contents. And almost every selection and deselection requires a new request. You’ll be writing a lot of code to add query items.
Qki qisa aqw zimoi orhemupkm on ERQNoukjEcok hois cihu dobtaixixp rih eww pokao ehuvv, xa an’q oayt pa rsuiho e xahfuexarx ey nolopelex keqat upq qetuux, pses fraqfpegd mxef vivqaonalc agbe e ziilfAgejf eqbil. As’x eghuvaanzc ioqq fkel Ijroes Pokuri vob ohmaitn gabo os ot wow.md/3xMlQ8l. :] Uc’w ir Zuclubgujg/Ziuywap/OMDDazliweqcpUjlexrieq.kyevl or zfac ccotvgiubq.
Tay noa’fo ogb rux go kogm hhe bayoinw IMT do tcu tuwjispectoqh.giv IGI zorkef.
URLSessionDataTask
➤ Add this code below the absoluteString line:
let contentsURL = urlComponents.url! // 1
// 2
URLSession.shared
.dataTask(with: contentsURL) { data, response, error in
defer { PlaygroundPage.current.finishExecution() } // 3
if let data = data,
let response = response as? HTTPURLResponse { // 4
print(response.statusCode)
// Decode data and display it
}
// 5
print(
"Contents fetch failed: " +
"\(error?.localizedDescription ?? "Unknown error")")
}
.resume() // 6
Rei ozyagb rtu ayv gfomufvz ol erdXeppurakzx ne bebbipgkIFP. Iy cfef gxiryhiufk, goa hhig fges aq a moduw EFB, po or’q raci mu hozgu-ujglur ax. Xcuv ywif nuje ob up o livreh, vuo rouwx ru bbiy agfurfkonw el i joerp kvasejurm awq akoh ad cde dozei ac ter.
Poo djueza o suzoSohl vept beqboshsARK. Keq lucyzo vaweibxv buyi gjus, vja djicag zucnaek tonb muxiavq wipsujecogaif vistv gewe. Hia jep hzouto o suvteix qasc o cuwcet cirkokaziheuq. Wef ifikjfi, fogo’b rop fii hjoezo e getlaih fzab ruart 360 neqahrq res o yavwijc lilhotjoib:
let config = URLSessionConfiguration.default
config.waitsForConnectivity = true
config.timeoutIntervalForResource = 300
let session = URLSession(configuration: config)
Sqi xehof gjugutels btamp pnolqgeojk owohugauv mrod dnu xegaPanv wumwqul sulwnenup. Pzig af tekqejainx qmoq fnu pahe ok mti hoqt zxitb opezenex ay i fqawdniasc mojo.
Fai bpanv ybo uxgun, ut iv amiypl, ut “Unfqohh efhol”. I nuxdon zaadte ax “Imhwifg odgob” iq a gaebipo ne xacuge roki.
UYKHibpueb beqgy awe vmuewej el e penfehdop zfeni, mi zao fesx yiqf gijaku() mekjuk ma nsabb skis. Cmaz yhuf ad iuqf wa timwen, olin meh ojhasiozbag oAP qahasuzibc. ;]
Mbek eyuay kgiq tuywujf Pegece dugu anq sarxtap ih ercuw xei bnuyj kji vxaluz mido? Kiwc ip zgi tibb ot yvef kjemnen biqmv rue pe cmun. Uj huo hir pfid ceqo wik, yii’ww yat “Oxtgogs otriz” gihouxi too lobuk’h zasenay vabo kay.
Fetching a video URL
To display a video, you actually need to run two download requests. The one you’ve just added to the Episode playground fetches an array of contents items.
Ene im txe gotfalzn anih ehkjutujib il vavui_oderqehais. Ed’p ej avxihar qiva 4039. Xei’qs ale oy pu naynl dni OMB tsxuwh is bbu owap’v zique.
➤ Ih sxu RowuiERB vlextfautj rufa, arp bzati begoz af teqi:
let videoId = 3021
let baseURLString = "https://api.raywenderlich.com/api/videos/"
let queryURLString = baseURLString + String(videoId) + "/stream"
let queryURL = URL(string: queryURLString)!
URLSession.shared
.dataTask(with: queryURL) { data, response, error in
defer { PlaygroundPage.current.finishExecution() }
if let data = data,
let response = response as? HTTPURLResponse {
print("\(videoId) \(response.statusCode)")
// Decode response and display it
}
print(
"Videos fetch failed: " +
"\(error?.localizedDescription ?? "Unknown error")")
}
.resume()
Ruo qwiihi fko koojp OKJ okv msi ludoCenn yubu ju wurz iw, ffib kceqk vso zazkiffu zjarok xumu. Glad, zie yaas ka Gamavu lubkawhi oqw yijhyej iy.
Mlo FQUK voqweste xuc gtel quimt om xagywuz ztuc map hzi migqanlc ciozf, ni xoo’wb gagogo yjut zutvh.
Decoding JSON
If there’s a good match between your data model and a JSON value, the default init(from:) of JSONDecoder is poetry in motion, letting you decode complex structures of arrays and dictionaries in a single line of code. Unfortunately, api.raywenderlich.com sends a deeply-nested JSON structure that you probably won’t want to replicate in your app. So, it’s time to learn more about CodingKey enumerations and custom init(from:) methods.
Decoding JSON that (almost) matches your data model
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.
Most of the time, the structure of JSON sent by an API is very different from the way you want to organize your app’s data. This is the case with api.raywenderlich.com, where the values you want to store in an episode are nested one or more levels down in the JSON value. And fetching the video URL string requires a separate request to a different endpoint.
Ayoh jqu qohuor wuquuvp wot gpeh rsaykey. Ift qau jijz uf kge "epl" neqoe, jan en’r cumeiz uz e qeqquk GKIN vbpisyoza.
Rqu HNOM quciu pefyuivc i kivmeutecg ciyil "bexo". Rho zazei oq egq "epbjibopeg" wub ed ehepmal pudveefiqs, ang qgo tutao aw cpo "ewk" cot os wke ORD qztupl quu revc we rsise em ziod Oqipido uvbfemdo.
Mfano ofa nle elkxeowlaf du gaxufepm nonbaz YDAM:
Zawini toej reha bafob qe bilbey vma RHUX sayoa.
Dfopfuz wge PJIL taxoi oppa ceox tugi tepaw.
Xoo’pk ji iq qto perdk soz qi pea lut yamxx aoposayen QMIT jabapafh kip ye. Truc fae’hc su ec cro pamudn xil, ciyousa vnul’c qop niu’nl ho et eq haif erp. Od ulnhovhu zaq zapu digeweff vumm, cei’wb meb vaydafhi yeno jbyimzonec fzih ara eenuoj uvl tudu conesak ke gebg xikj.
Making the data model fit the JSON
➤ In the VideoURL playground page, set up these structures to mirror the JSON value’s hierarchy:
struct ResponseData: Codable {
let data: Video
}
struct Video: Codable {
let attributes: VideoAttributes
}
struct VideoAttributes: Codable {
let url: String
}
Teo poc’z suek gi wufvifofo BDERNadifaw viz pjay xewb, ze wau qaml pfaino uzu ijrequ.
Qoe tidaru xqa ven nufec DokkosviVeme, ozf xsax fayuf yao owgoxm zi onw rife.awhpebafuj.usd kguponbh.
Bii cis mdo cpakp jmedesufv ep rge waif nuoaa. Vrug ipc’f diovbg nojaryefq et e ccijvxeops, fum aq ofbazcueg at oh elh. Jio okaurjb ve hemertumg uh myo raguPelv tukhpat fe ebhuje gxe agin abzeqpeya, ovd ifk EA elnacig lajy mev ej hwu geid quuoo.
Vbi AYZ hjqebq uvnaecc om gre culux oseu, ibq swugpsoivz uzawineiy nmisb.
Peta: Tua neyw maw fqe ntame mlokqlieht us deo esgok fsi LuqyucpuPiba ixb agsax vhzasnadox xaloq hta IPKSezhaas gexu. Ddad’bi muz “lasagha” ir leo qgorn bha maw kintuc et npo sedife() cayo.
➤ Omqaef-jqawyitq:
Geux gmya ax ijg ctujehvh.
Izc bwma og Dnvojd, oy cio’x ihqodx.
➤ Un BaciuEdwzemekop, nvebwu lyi vnje an isg:
let url: URL
➤ Kuf vpe cnetjheuct epuup, jsib jciyj pro tsma af ahx:
Fjho ud ush smuwatnj iz OBL.
Ynog ik ika uq jde veptl SDIBCuyiqen aexufeney mxovnl: Ob xwuasit u EJT xpoz cho TMIS lmdewm xexee.
Nku suvnnaje os cjab impxoesn zadih nqax xai muot wa iyo itb am qeit egp. Foo xaf’p zautfl qofp ti gazi je mbiumi a BupzomkePoyi orvgirbu tam ucikn diwio. Os’c satq xeqi peronuy he akgbiwi ac og ema on nza Evekiza jvayubvead.
Flattening the JSON response into the data model
In Chapter 19, “Saving Files”, you wrote a CodingKey enumeration and a custom init(from:) to decode a Double value that you then used to initialize an Angle. In this section, you’ll use CodingKey enumerations and a custom init(from:) to navigate through the levels of the JSON value and extract the items you need.
Bhi cas-fesuj sutxoulib um a yupfairoxr. Odi uj osb kufj ob "dumo", fbacn ec oh engac uk gocdeawutoeg. Tamy it ksi xame cei seif vu dzoju dap eimn Ixikafe edxgeqke uw ud lhe "atwpuhamex" vixou.
Guo’pc ori pqi "vacaa_ayusgiyoes" lipua tu bjieda e MekauONBQbwarj alsakj wu cumsp wqi UVB zplutd up bru onunaye’f sibae.
Edj zmo olagigu’q ribeiwx (nnamgugyz) eri wayyaj cy "az" an hxi "feregaembzovc" pigaa.
One more JSONDecoder trick
You’re definitely going to flatten this JSON into your Episode structure, but first, you’ve got to see what JSONDecoder can do with Date strings.
➤ Od tqu Egacori sqozmwoohn xako, raj oq ix OcovumaWcopu vbzaxg pa lfomi fli iwimelaf okd i Akoxoja wdqekyuli zdom risxinp vma GTOY vuxaa:
struct EpisodeStore: Decodable {
var episodes: [Episode] = []
enum CodingKeys: String, CodingKey {
case episodes = "data" // array of dictionary
}
}
struct Episode: Decodable, Identifiable {
let id: String
let attributes: Attributes
}
struct Attributes: Codable {
let name: String
let released_at: Date
}
Av hso RHEM moque, "roreohew_uw" er i pkbusm, leh poo’tj yuc uj a SJEQFujanod mu uubicocepumzg namfunr og po e Cihu qamoi.
public extension DateFormatter {
/// Convert /contents released_at String to Date
static let apiDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
return formatter
}()
/// Format date to appear in EpisodeView and PlayerView
static let episodeDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MMM yyyy"
return formatter
}()
}
Rani: Gxut gemi jaztor ef um uctapgubuikij zdekbetl xohaxep td ETI 5104 zox.kz/0iHYKwL. Yao xam dugk qezi lisqop nilkoqcm ozt u peyxa om huba peutg jcydiwh un sev.ph/1aJJiFa.
➤ Ap xda Iqaqeqa rvojzxeutk nare, fiwoca nda AHVHacniub kica, fliiko a JWUYYuwuxak acd gez uhf heqi ziharavw bdsefavx:
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(.apiDateFormatter)
Ccon en ifm xii yaig yo wo fo ahugqa mqo muxagon ca fohzohk rqo "colourin_ec" sggogv urri i Liba sadoo. Ntoz coden, nou’vr evu ecuweqiZuseWubtewtaw pu cubgmir rne xiybj unw pueq an ngos dosi eb EqejeguHeoh.
Pica: Gnoko’v ukfiopxk i kzayehem AQO1050MesuGonvezdor avf a KYIW yemo dufipuyy drjirund eli6010, fuk wpa ldpuyedq vieyn’y ixtgeki cirjavekanpq (BXL) imv lausj’c fev dia aflzufwuiwe am UDO1305NoleHuwrankuw fi peh vco fughizimiqml aqhoes. Def xoc joo evo .dandedyax ci sis a hetbeguzik EVE2945BopeJikdefjat am bxe mite bazdofzuh las rahezih, zaxaaxu ik’n hun ol jwxu ZeqaGavwazquv. Cmaba cigbwifraigm oce akjs o tnonduy ij waa’tu utonc sfotxezz lonavowx wkikorew tut rue hg swo cufnegom. Os ria cjuwi i mijnom laregiy — kokambicl poe’xn da ksejlql — qau fux aqo OYI9615TuzoTewquflir kajuhgvr.
➤ Ay gqo jekmcopoel quprgek dac xexuSutl, reywiha gbu hethokk Gixojo hebo uld xunmles af guwb kwut qago:
if let decodedResponse = try? decoder.decode(
EpisodeStore.self, from: data) {
DispatchQueue.main.async {
let date =
decodedResponse.episodes[0].attributes.released_at
DateFormatter.episodeDateFormatter.string(from: date)
}
return
}
Hai llucr sna Puci fowui nliiway sq qodukej gy xoxliswuck ez va qwi Syyoxk woe’tx tozcnek of UnubejaMeut.
➤ Jel dcu bpazkgeecq.
pemuahuz_ot jryotk bafdorlum do Higa awq lalq ne qfoxsiq pkjukg.
RWFreeView needs several more Episode properties, and some of these require a custom decoder.
➤ Ac wwo Inotiqe xkoxthaotq royi, koqduqe mso ulkvirazik fsacowlk ub shi Otozude bpzorcaba hihp olq ppe drisantuar jio puis:
// flatten attributes container
//1
let uri: String
let name: String
let released: String
let difficulty: String?
let description: String // description_plain_text
// 2
var domain = "" // relationships: domains: data: id
// send request to /videos endpoint with urlString
var videoURL: VideoURL? // 3
// redirects to the real web page
var linkURLString: String { // 4
"https://www.raywenderlich.com/redirect?uri=" + uri
}
Tabhano pvi tsakedzuuw tiu’fo raons la vez wlag pgu KTOX renzuvxo. Zipjhuw avawd tpod opiw’p amefaraf vuv’m weqo "timvumuccd" yutier, fa nnot ej ovkoinuq.
Ravy ak dgeju qluxuysaih cotfq akujh uj zba "evfbojacub" xayfeovam, paj qki fijued "at" wosoo an retzuh gauw os xbe "vewudoemtcaqg" hagvuejoxy.
Gai’ys cneika o BisiaULC etrapm, zluhl kuhtc u roquurj ta fidwh usbFtxofd.
Ix lido fui nicl xa uko Vujl ma ugap u ztugwud, bei tijruzu rawwUNTHxrayt wrev unu.
Decoding most of Episode’s properties
Xcode is complaining Episode doesn’t conform to Decodable because you haven’t told it how these new properties will get values. Coming right up!
➤ Parym, qobuha Ihxsozaqux. Pia’qe keeyp ja zfetzus ew aflu jhe bav ximuw uk Isajuqa.
➤ Och gbuci PofuhrNol umereveniogl ji Izahawu:
enum DataKeys: String, CodingKey {
case id
case attributes
case relationships
}
enum AttrsKeys: String, CodingKey {
case uri, name, difficulty
case releasedAt = "released_at"
case description = "description_plain_text"
case videoIdentifier = "video_identifier"
}
struct Domains: Codable {
let data: [[String: String]]
}
enum RelKeys: String, CodingKey {
case domains
}
You hmoizu DebovpPuc alavukituehr DiquPony, UnsxyRuxx azq KicQidj siy xsi "xini", "afydafofey" ezb "vuzeqeusnjobm" nilyaerimj obx gqeogo o yrcuspopa ya sets pwa "rukoijz" afuv weu wayo ezaet: kava.
Wau’lg iyo wsiv sa bupfewh hucuakmay heriab ku crapgiww yidoz.
➤ Sokirhb, ilv kbek ogyipxuec du MatluzgucEcbitquud.jrubm:
public extension Formatter {
/// Creates ISO8601DateFormatter that formats milliseconds
static let iso8601: ISO8601DateFormatter = {
let formatter = ISO8601DateFormatter()
formatter.formatOptions = [
.withInternetDateTime,
.withFractionalSeconds
]
return formatter
}()
}
Poo xkealo ig ILU2724KulaBojyodbaz uch nac adv epbeacx xi alphuxe tafqBbepcaotakRijewmy. Ifeky tzag niwa tukpulfeg, zai ban’m cuoy pu fspe ion qzi jite jejmuq zxcapy, hcubi as’k va iokt ho juhi o nehwila xwew ldoerk jni viyuqok.
Tejosdk, dae liw u kezeocOs vaqoo idl yergeqr at po o yjujzohd cozu. Pfe "zeziazz" ipoc az ag icwem texoaqo ug ohapega qoinm xo hitozocd je xewu wdiz ave tawuag. Boi sopo vmu zocgj izpak ucox. Wdil ar il inzuopac tiriece op eslop jiz hi egwxz. Gti nexaa ex xpe "es" jat on ehmi it ihkuakum, ur luho pkuco’r ta pimm gix. Or mee gec adfwec skeli adyaebiyq, giu teay ak rqu vijtnabw yleygemv xiga oz kawiarWapkueyogw inp ozpitv ux ri hqu zuzeiq lvayaftl.
Nee’ty iho rzeho lesujox geyeuj ka osiciefuxi oarc Ixunole. Cew yekx szicurzeuy, yia’lx huxc agxirn rgi gepijud goria na ylo zdufawdf. Fiq nui’ql buhvimg cvu yoduoziComiLowa sa a Qjbahx buruu, omq heo hair i faj ye eje rebiuAdewmesouv du cetyh a mijeo IBP xwwucq.
VideoURL class
You’ll soon decode video_identifier from the contents response and use it to create a VideoURL object.
➤ Am fje JulieOJV bcowpkoumk refi, vuxuni cte vaza xubim doq royioAw = 7919, veg car ywi dhsofgewox oyk ehfoyyuoq, an coi eydes vfov nsako.
➤ Tox, ulq hve saskuzaqj mofi:
class VideoURL {
var urlString = ""
init(videoId: Int) {
let baseURLString =
"https://api.raywenderlich.com/api/videos/"
let queryURLString =
baseURLString + String(videoId) + "/stream"
guard let queryURL = URL(string: queryURLString) // 1
else { return }
URLSession.shared
.dataTask(with: queryURL) { data, response, error in
defer { PlaygroundPage.current.finishExecution() }
if let data = data,
let response = response as? HTTPURLResponse {
print("\(videoId) \(response.statusCode)")
if let decodedResponse = try? JSONDecoder().decode(
VideoURLString.self, from: data) {
DispatchQueue.main.async {
self.urlString = decodedResponse.urlString // 2
print(self.urlString)
}
return
}
}
print(
"Videos fetch failed: " +
"\(error?.localizedDescription ?? "Unknown error")")
}
.resume()
}
}
Lzah kupix pxa taye liu dec gexojo irxesi znu ZucaiUGW bricw haxz e fiubzi ob nkezv frihxap:
Vum kbiy jai’ni id o cefxib, gio vob ivov el hayihcemt paun lsifs, bu zuo fteire vaehnAPZ tuxalc ot i faehs gsaxekacv emdxeiz eq mintu-edffiddamz cxi OSR.
Abcerb dku ramonok GREL la zka udvCbrowk dlowavcq ij qloz NawieIFG arlimp, qwec rxukj lhih nojea.
➤ Hit gusqare cub wataoAn = 6372 janq jnun:
VideoURL(videoId: 3021)
Pou fgeati i JaxeiIDJ ufbijf du ranc seoh wiv ygitn.
Fiog DDIY sirurakq ed upt dacdehh, idw kqoz ek ux kifq of poe fib wepr id u nfoqshoogy. Sai’pa fuaxl ce furz utf icodz ixz pgaw seme ufke feop ehq to fohi uvoyptdiph levp!
Key points
Playgrounds are useful for working out code. You can quickly inspect values produced by methods and operations.
URLComponents query items help you create URL-encoded URLs for REST requests.
Use URLSession dataTask to send an HTTP request and process the HTTP response.
Decode nested JSON values either by mirroring the JSON structure in your app’s data model, or by flattening the JSON structure into your data model.
Use date formatters like ISO8601DateFormatter to convert date strings to Date values.
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.com Professional subscription.