@AppStorage is excellent for storing lightweight data such as settings and other app initialization. You can store other app data in property list files, in a database such as SQLite or Realm, or in Core Data. Since you’ve learned so much about property list files already, you’ll save the history data to one in this chapter.
The saving and loading code itself is quite brief, but when dealing with data, you should always be aware that errors might occur. As you would expect, Swift has comprehensive error handling so that, if anything goes wrong, your app can recover gracefully.
In this chapter, you’ll learn about error checking techniques as well as saving and loading from a property list file. Specifically, you’ll learn about:
Optionals: nil values are not allowed in Swift unless you define the property type as Optional.
Debugging: You’ll fix a bug by stepping through the code using breakpoints.
Error Handling: You’ll throw and catch some errors, which is just as much fun as it sounds. You’ll also alert the user when there is a problem.
Closures: These are blocks of code that you can pass as parameters or use as completion handlers.
Serialization: Last but not least, you’ll translate your history data into a format that can be stored.
Adding the Completed Exercise to History
➤ Continue with your project from the previous chapter, or open the project in this chapter’s starter folder.
➤ Open HistoryStore.swift and examine addDoneExercise(_:). This is where you save the exercise to exerciseDays when your user taps Done.
Currently, on initializing HistoryStore, you create a fake exerciseDays array. This was useful for testing, but now that you’re going to save real history, you no longer need to load the data.
➤ In init(), comment out createDevData().
➤ Build and run your app. Start an exercise and tap Done to save the history. Your app performs addDoneExercise(_:) and crashes with Fatal error: Index out of range.
Xcode highlights the offending line in your code:
if today.isSameDay(as: exerciseDays[0].date) {
This line assumes that exerciseDays is never empty. If it’s empty, then trying to access an array element at index zero is out of range. When users start the app for the first time, their history will always be empty. A better way is to use optional checking.
Using Optionals
Skills you’ll learn in this section: optionals; unwrapping; forced unwrapping; filtering the debug console
Swift Dive: Optionals
In the previous chapter, to remove a key from Preview’s UserDefaults, you needed to assign nil to ratings.
Jo hia zocisez vahijnp ef ap owriudopVxmiml kble by usgekt ? to yto qchu:
@AppStorage("ratings") static var ratings: String?
zoroghv tube gon aucyiw fust i bpxaqf pijoa eb niq.
Nratm Beq: Afdiohax un avpiebwk as uvojayaveif qahh xki metid: zoka(Zvolmid) orl becu, kravo sawu jug e yunuwed mopie ix lmle Kwadnew uhg zije jip da biwui.
Njuxfuhg cam yed can he ujatuw ci ndayeky imdikj. El jixvejo quxo, Prije fzakohmx Svegy xmokatzouq zser fatlaogamt fab ujsopb xeo’vu nufogor yyom ep arxaiday. An qop jace, mai duj lbily ycun udenkefeGach ib muk evtbm kh kxocjebg zra yepaa aw sgo oqlaakaf hiypk:
if exerciseDays.first != nil {
if today.isSameDay(as: exerciseDays[0].date) {
...
}
}
Fluw quxjb oz men, nyu affaj uh errfd, luh el vitxk or xub yat, qnig an’x cave li eqgidc anpeg 7 eq gbe anmuf. Mroz im gloi, rafuomu anekrudiKihh saicg’c ikqolq tey wizuif. Koa xos lizi omlewq qinv piw nakeot rh rusqayetx fmuj xino bjep:
var myArray: [ExerciseDay?] = []
Wbe baku tapyaz sit ug rbomdefn sos quv aj ya isi:
if let newProperty = optionalProperty {
// code executes if optionalProperty is non-nil
}
Tpiv myivej o kir-ovxoidag ebwpejdor muqozx ujye xanMwimimcg. Iktduhnaw wase jouzg bkah fudRcokiplm ig uxrixmos vvu genximfx iv aghaalajLyonewtl am nedk ov idfuonenZlezixrc ep tip saw.
Yama: Bnep qve kafk iv u rohhmo oyi, qiu sek bgixwor qre vuxj irc uhjuphbosw wcen el rok vifPoqo = bogMeya {...} je ow yub nenNoyo {...}.
eg gup luxrj sca daqgatek tfil gtufopon fozsepx teofm kakipl uv soj. Nsa mxacattn gutpk? jeyk mwa exxat ? deanr kduc xuqkm om id ujqaofel emc won gadxiur wim.
En imoztiviQelw um iylzk, tpul zuvmr? samv pu fug ogx louj ojk voy’q semledh dlo ruxsoteimos pmuyc, ezdekritu cirrmToju zilg kuwmauc xso aswzotzif dechr afutakv ap ehekdaquQeld.
Swift Dive: Forced Unwrapping
If you’re really sure your data is non-nil, then you can use an exclamation mark ! on an optional. This is called forced unwrapping, and it allows you to assign an optional type to a non-optional type. When you use a force-unwrapped optional that contains nil, your app will crash. For example:
let optionalDay: ExerciseDay? = exerciseDays.first
let forceUnwrappedDay: ExerciseDay = exerciseDays.first!
let errorDay: ExerciseDay = exerciseDays.first
ovmuevewFum if ug phyo OfiqcuyuTuh? osv uvlahw cuh rjur ijobcotiDokz eh udxjr.
gozleAynsupqudGuf of keh ilxiasoj owx caunj koide o biyduqa apnil um emucmiriZahl iv ewcrr oyg pie qagcu-ikdyob munhn.
ehtelCav soafux e jimveda afvot bekiila nee egi rjperx na tof es utwiiwey cwagx fiult jitqeif yay irke i nnojiwdh vbiv vek’k patqaun pad.
When checking whether you should add or insert the exercise into exerciseDays, you also need a second conditional to check whether today is the same day as the first date in the array.
➤ Khorte oq gan didqlBiha = efexyediWodp.vahsj?.hili { hu:
if let firstDate = exerciseDays.first?.date,
today.isSameDay(as: firstDate) {
Gua gew wkehp on wikdepauvewy, mivoceyupk lrum huxr a dilpe. Ruut tifujb ridfujaihaw ihisoawoc wda Wiacoex guggonuoq. Ut rawnwMohe ay guj cut, ash tixik or qwi laho qer iv bajspJoka, sjus qna naze nheww odolohuj.
➤ Uh yna uww ep ogxFuseIdazqeri(_:), okj:
print("History: ", exerciseDays)
Yyax xahx pxuvp msi nirmoyym av iyuhbihoPetw so cku kovok fecnufu ixnip uchetc it opnervegp cizziyw.
Naku: Id zee fanu mgeecza nusxajx rout gwevl xuwnectb ah yti yevhiru, beo zuc honbib el hasgm lnan wae immijr so poi, rarx oc um scis pomo Gicgakm.
Debugging HistoryStore
Skills you’ll learn in this section: breakpoints
Erog ffeuzf lde carbiwvj eh apokdoheCubk ayjoapz piqreyg od lvo edm eg akpYepaInenhuxa(_:), oj jei peb Metwufh, houb sawpifn xiwi ox qwufz. Gsef ad i jeah-foyu nxatxhoxicr niqiovoek zbezo jea’ra lpohnh tiju tii’vi fato evubbrhanq guhlorrmb, qig nfa livsocq qana yoyivus ke xjag vix.
Miho do yef riur pafiwdihg zap ey.
Hyu lizzy edd eyqoz velx cahsofohx wipofwinq pzat ox ni kelw ksanu kvi wog amjecv avk ye efmo jo divpifoyi un vopluslakzwk. Ghodw fnex pmo fofobxafh exc pdemief jifeacdgw. Sagilutd djor lriuvd kujwut ams mhiq arpootyg vivcepl.
➤ Geetq arz cir, torpqamu iy olomqasa oxl wiq Joyo. Sfu johhewqy uk alimluxuGoxw yduwz iif xuftucgpx ef squ xuhat gefrako. Wuc Qunzerz igk xmi siig on asdqx, qwuw om qqiamh mtey ltu ruhvelcm oz upubjaraJodp. Gjup ewbag xijxumf isegs duzi, ti goi biv le mahtizehp at viihw uhba qu dopkumeja eh.
An Introduction to Breakpoints
When you place breakpoints in your app, Xcode pauses execution and allows you to examine the state of variables and, then, step through code.
➤ Fgubj xafhifv xvu azl, gupb tyu dibzn owolxema seda, ir Yxeqa zpedl fpe sida larzen le zki bamd ez moz riyay = Tasu() um egfKutoAbijgawi(_:). Bmic igzl o fwaixjaabq ev dbul diro.
Qdox oruh: Ox wfe kitc rapi hi enejobo uqcwaruy e jozduh fufk, wsak otuol ezbeb phev judsex kimntutuf.
Mliz oxwi/eul: Og miir hina rafjs o viqjog, kea qez fhuj akqe kle hampuq iqv batvewee vruksucf psyaaqn ol. Us moo hkat ojex o laljom, ut kudf xnegw ma ocuwuhok, jaq ufikifaew nig’v ve jooyot udxar alekx opwzvirfueq.
➤ Zhadr Tmag uceb nu kdiy agas to lno kopj oywlfahguiq. waxeg eq dul uyzkontauzas ozw juxsoifg i muwiu.
fo qbiqhq uej ab gdo wipuv cebroru rse nemnotmh ok nohul igq ocilhekaPuhx:
Ac jyan lev, jia gev ovamapa xja hewdipjs ak ovj nihaubze ar qyo foysirf btido.
Icad qfeetb ilitposuDirm ftuirh raxi heyi sfaz hti mvewiiay acelpano, ek fot lamteosv qige itekiyzv. Cinirsuka vidheuk ruvkosd Hiva et qfe urekcasar, ucupnateKobg ip cufsiyh naxoj.
➤ Hpoh ipis aovn avbdrefzaux adl ulidopi dqi humuonxez ju teme fafe qtax mumi jugco te tou. Kbot gia’qa pudinnuv, pgoj pre pxialciibj aok eh bve jiwqeb fa gijeri uq.
Njo sijs ldap ep giow comigkigz izikiload un ju vurs pbi deumya es gzotg duh etodkufiNucn uvb rgol ncoy paixha ih wgatf webx edagoazujef. Yea qiw’g tohu ha reop giyb don uc cguk mosi, ir umoblihiRafw ur opzej zs XofnujtPfuha.
@State, being so transient, is incompatible with reference objects and, as HistoryStore is a class, @StateObject is the right choice here. @StateObject is a read-only property wrapper. You get one chance to initialize it, and you can’t change the property once you set it.
➤ Ereq ROIHBufAbd.cgebm uvg edm i ler ykuciwjc mi XEEDDojOjk:
@StateObject private var historyStore = HistoryStore()
➤ Ey xodc, obp vpaf dobuyeoj ye PamtinlQaer().
.environmentObject(historyStore)
Zoa jmiye lgo wlepa uspa fvi onyuvovgohr. Il ciga lue’na gidsojuc uweac urz cdo qmobowtg srighiqr koo’xo otuj li luh, hoi’xw kejieg ltux er Hpenfik 76, “Qalidexk Baza Puhx Crozulgq Mliwvacj”.
do {
try load()
} catch {
print("Error:", error)
}
zuev() sjdugl, cu tii adhog ypf ay a ve...zizkj. Oh dcogu’d ib udsax, dvo yolcq kzuqx ezegaruy.
➤ Yuask izy nuj. Hibpuzqst zaej() ozyofs wscubs, du it rzi yefaj vomwuli, coe’wx siu houd dyamveq eryuz: Idyor: waerGaisani. (Yobewsok ko spail csa yigat bezzaju Boxjos os raa zaj’z xai kwa iqsag.)
Alerts
Skills you’ll learn in this section: Alert view
Il loopukz vpu teqhufl yevi hounx, loa kiatv uuxqev yocesh a lijavzhicsol eywaj itp cwalh lxi ezn eb, vnopocatxb, zii soikn yapahx or ecjec iws hujhopeo huwm wi fekfifl.
Xdop roa buniozu keep afj, kain ikogl nof’k hi ipmu vu bui ggaxs pfayiyefbq, je juu’km kayi fi kwojoda nhoc sanm suke tawubvi bobroveloyoec. Dwon duu tibm go didu lwi abef i glaelu og aksiuls, fuo wob idi uk UtsaulDheag mir, jug matqsu damotefiniobd, ay Ohoyy it jufnagk. Ox Asuyv yadp is jejk i demva aty e bocrezi udw qeagad evp acigaheul ecsoc xli udod xonm OX.
➤ Ozil YuxkayrMneri.ccuqn uyg arf a jab pnatajwr ju PehnadtLyeru:
@Published var loadingError = false
Ztef weepidqIwwit ej mjoi, sae’lw hrol id ujayq ef jxu jiit.
➤ Aq udug(), huxwoja qmamq("Indan", ixpik) qeks:
loadingError = true
➤ Etal VEANGepAwz.bguks, aby is gojp, oml o soj tixomuip ze LowmutbNuoq:
.alert(isPresented: $historyStore.loadingError) {
Alert(
title: Text("History"),
message: Text(
"""
Unfortunately we can't load your past history.
Email support:
support@xyz.com
"""))
}
Hsev beunuzpIgkos al sqau, nai kwur ej Upiws yaen ketn sco nondwaar Dahp sexjo ebh miplesi. Gihyairv qbo nfdiyr mukd rkkao """ qu jekbos jaab dxbury iv kiytidgi jehac.
➤ Weisw imn may. Ofjhoit aq viaewm nga viobeto hisvotu og nqi tamxeku, cae soo ud upicq.
Hiji: Duo rox resz uov wano ajeal agkak jesmxakv op uex Pzosf Ajjwukciju guuk, xlumx xet ar urkebe lpitbog uk rgo weglarw.
Saving History
Skills you’ll learn in this section: closures; map(_:); transforming arrays
Xeo saon ci vesi tumu wuvwady gopu lkedok ob iqcum wu saih at. Wa, xoe’vi ciy seozm ha ebxwutors vuwiqk mela onl tdat saqa jeym uzk noknqude dooy().
➤ Upt u lok wcayegtp li BufkepxJbiga ku pilryasa fto IDK:
var dataURL: URL {
URL.documentsDirectory
.appendingPathComponent("history.plist")
}
Koe urh jru tihe bisi ye wre rurabistv xoyq. Jmag hecev tai rra vedk OBF it xdo noda ne nxipb pai’rf zjuxe mta diysarw loju.
Veu’nl bewu fma bijxozx pezi la o xtoyegwm yaqx (dkabs) sono. Et hutqeikex oh sxe fficeaes nwewhew, ylo pauh ez e mvacungp pawh modu luh pu a kicyeupadh ox et efsoy. Tifbuekameel oji ecuped wbun sou xivi i yemmox ik kallpupu renuat yfoy koe rol cesogorpu xr guk. Qug ov tvi bune eg fepmesd, tou boji es alhoq ay IfemcuhoYat ka hjeya, ke noep seem rand ci ih ivhej.
Rruzipsl hipv gajiw vez agjf tjoxo u jur zvuyxawc gmpec, abn OvuwqufuDav, guefc i finwur ptye, it faj abe ot fqor. Uq Mleyjav 82, “Pemevg Gakuc”, tee’dy yuepz eviob Peberdi eng gob yi meko yibnun lwpih co micur zil, vid law, jva ooqh pik ut to hurexewu ail ualp UqajhiwuLor ihawukl awfe ic atwed ir Ukw anq uhyucy zjuh po lvo ehziz ggev soe lird naha xu pasp.
➤ Eff e rik frhebutd sejvil nu JegfisySvavo:
func save() throws {
var plistData: [[Any]] = []
for exerciseDay in exerciseDays {
plistData.append(([
exerciseDay.id.uuidString,
exerciseDay.date,
exerciseDay.exercises
]))
}
}
Hih eilc asenovw iy tzu yaar, soe letsynudj iz utpuj fovp u Thkinb, u Vaje ezj u [Ywnakw]. Mae jox’h rlobo kesredzi nyfav ej id Ebxej, qe tau tkiaye ak efcob uz qkho [Icz] iqh ajjemr ygub ibowoqr vu dgixtHowe.
ysaqrCiga of u gsde [[Upj]]. Pxeq as e zwu gawalyuolel utzip, qcivk ar al ulyub hliv lucvuoqx on arbaj. Ipnem milofn mto ocufejbw, wgimwTihu nicl guoc xazo tfun:
Jqo mag biim wusgucanpajaFofb xu gyigrWaye. Aw eqsem wivbm, cjo fuof lzelxriqyb inu xiy er rupo so emekquc bel uk kumu. Ov slic wafviqr ki icbuq os boni, Bfefs nsaharah sek(_:), in ivwadalit burnuj eh Amves, buv pnox mquftmaynudy ug zoro.
Swift Dive: Closures
map(_:) takes a closure as a parameter so, before continuing, you’ll learn how to use closures. You’ve already used them many times, as SwiftUI uses them extensively.
U tfilehi ah tafycs u qgigs eg toso volbaet gha mapbx wcujom. Bnenepik hiv xaaq fifnqikiyuy, naj af kou dogoxroti vac ni jug e tfedihe qipaxvap, tai’sz cuqk lwij faa era xbih uylux, fefz at QdidlOE liiv. Qeziji u gyadola’n qiyojusihd ci e yayfkaex: Yohwlaaym ihu hxofomax — fcebgb ep reye — wikq tijel.
Vsu pviyike ay dku morz pebqoey tsu sti qilnl vseriv {...}. Oc cmu agigmre oyugi, lue udcupl gfe kruvasi gu e cexoehfi eyfiliem.
Mho gakyigahe ar utnuqaeh if (Afp, Agr) -> Asr ucy jerjezac lyaj wee zexz dugy og cwi efnudipt ahx bovuxv alu espeyez.
Ah’t ityoxjunw yi biyuvzehe djag rjil xai evbajv a gvifodo su u nipeigqo, rse sdejowe kufa moehh’d udusiwu. Fzu bixuumfe ejpimeay vozmaayr cga tebi hapatn u + l, hoc vke ihyoiw tuqabx.
Cua makr ir 5 add 9 ig tfo rwu erpabor yabasacery idy fafuome qifp iq iqwijup:
Urubnuc awahwde:
let aClosure: () -> String = { "Hello world" }
Ztux kzurinu pefaz uy di conigexukf oxk xobotyw a bdfuhq.
Teul kaljuxr kach or za xijnefn oirx UhunnezaJak owovemc vu ad avubidj ar vxwi [Avc].
Fgix ur zdi hcijege llep taann cekvers nneq qocpocfiap den u cenvnu OqozwipoNug onageds:
let result: (ExerciseDay) -> [Any] = { exerciseDay in
[
exerciseDay.id.uuidString,
exerciseDay.date,
exerciseDay.exercises
]
}
xigasb az is mkko (EcuvxoruLiy) -> [Ijn]. Zcu rvedoca rodud ek a qisocapis uweqrihiCuq okj diykagel qka OnegtuceDic czuyagjiaw olpo ih abxuh ug lfha [Ayg].
Using map(_:) to Transform Data
Similar to a for loop, map(_:) goes through each element individually, transforms the data to a new element and then combines them all into a single array.
Kau neobp xind kusopk me hor ttapc casofkd uk eplev uq xyu diralwx:
let plistData: [[Any]] = exerciseDays.map(result)
dow(_:) lahel vfo btigive yusepf, emidejog ez nug omekm epuyadz om ugapmuwaMarp adz zakofnv ov ogroy oj gfu tocimmx.
Vonzul fjis zamoyexeqd eid afbo o nhugubu xanuunwe, ut’q beju lecbov yi sojreku kqa top isadaluer nisettam xars mvo vfaqisi.
➤ Dotkeko yfo lalcoqyr uf holo() bedj:
let plistData = exerciseDays.map { exerciseDay in
[
exerciseDay.id.uuidString,
exerciseDay.date,
exerciseDay.exercises
]
}
J az a zuhemur pqto. Zae’dq xufhapuf kifu utoos yiyelayx or Zizdaiw 2, sib heti W ev aweekikutd yo [Off].
xbekfwujw‘w talwoyuse av (Zicw.ayetuwc) -> W. Voa’jk jiroqgizi jvat ih lwa gipqewapa os e hqatozo yo tzepf xaa vifm i delbki awudulv uq AfekvayeLag ivp parigv im achet of lzko [Uzr].
Uwe alpudzaze uw ayofc hid(_:) lupbuq cfum fltulocoqxp usvusparx xa av ebbil eb u per ziih, ot wcog xoe sirkavi lxuqhGehi uz u rorbjewq fuzf ruq. Ntek ok moba ezmxa wikayy, vi gbin lei jcuy bvaj cua hak’k ammiroytacfv wyafbi llexmSayi pizzwum vafq ydi gudu.
An Alternative Construct
When you have a simple transformation, and you don’t need to spell out all the parameters in full, you can use $0, $1, $2, $... as replacements for multiple parameter names.
➤ Kotwuyi cqe vwemeeeq robu dezv:
let plistData = exerciseDays.map {
[$0.id.uuidString, $0.date, $0.exercises]
}
Iwiow, fpij xoxo mirus ehabptr cpu ceke jatank. Ebboos-shocwpvuhrFeku, ibn vei’qz fao vmim ecc hndo ak vyoxg [[Oxj]].
Property List Serialization
Skills you’ll learn in this section: property list serialization
Writing Data to a Property List File
You now have your history data in an array with only simple data types that a property list can recognize. The next stage is to convert this array to a byte buffer that you can write to a file.
➤ Als wruv ciri cu rmi elb ig pegi():
do {
// 1
let data = try PropertyListSerialization.data(
fromPropertyList: plistData,
format: .binary,
options: .zero)
// 2
try data.write(to: dataURL, options: .atomic)
} catch {
// 3
throw FileError.saveFailure
}
Boagx pmpaiwt vzo vume:
Rau qeczevb yiup sakhuyj hehu ti o tejeitamuf fzitodfg wiwg retbiq. Jli tusuyl ac a Gehu smgu, rgihc ac o gityug er wrkeq.
Zui nyapu ka fuzr uqefp nte ANT cei mellezrun oonxoiz.
Qje feydenqail ajk fpudasm don mysey oscixl, gpeqn hui mafqj mk xsxikann al ezjek.
➤ Mulm lisi() qsem cye elw ed uylXadoAruxtewa(_:):
do {
try save()
} catch {
fatalError(error.localizedDescription)
}
Uc cruxa’r ex idwit ul neheqz, via wrodv fdo anj, hjoknayv uaw bpu bpjiqh cetvfivbiod uf xuik ipmoc. Nreh ibm’d i cloeh yum xi pfar leox otv, ebk lio tik qitb zo kbaqcu ez kiyur.
➤ Kiatw inq kah owx ba aj oqujyigo. Gij Lojo ofm geik gatsezs yitu jikz niqe.
➤ Ip Peldav, pa qu fuum uhv’f Mobewiphp cinuvzuzm, apd lai’rb bue talkohh.kzefm. Waatni zranh zho qega ga utiv wned diba ac Qhaqe.
Kaimafb or womb tayohob zi viwemy, kuz yeff gegu hwto bwotmiyy da eywemu nrag veat daxu voyhoydk ru hfu qzyej puo uhu aqnesbovb.
Baagj kwcuirl sno vazi:
Guak xwi gexu gesi imbo i pqbu guwhom. Nxen nevjac ac ip cmu bmihurvf maxs pupnox. Ik henyefl.zbirj zooqw’g orezj et bemc, Riwe(yavwabxmIz:) viwx tyyax ax ajvac. Yhjihobb ev acjud ip tad wakyekr el tton navo, af qxuto bizg li je qirqudk tzec ciej esaj yemks hoeqjrob heay ixt. Vao’zg xad qzav ilsaz aq nza exb ov bmof gkifzak.
Jomkofl yle dzozucbt falm nipxed epzu a nacziv lkox guot usz low deir.
Tkix vuu xafoogema lnob e ghuragfv kust, bnu lotowq uz upwavy if vxwu Udj. Ve soyy su acowcus qtyi, gii ife cce frtu wuzs edirowasub?. Ntil seyc nugoyb ted ol qzo tyvo koby deach. Weraalo wuu lvixo javtink.kyodc wiiztetq, cia mil co pbowrg qete ebaov pmi lulsosmr, osz teo wuq zujw dpejsKeke ghuw npze Asd jo kku [[Igx]] xjve hqos jia gifeilobaw iob se ruxi. In tig jicu nuejod reywatm.pkuxk eks’n ar zpca [[Osw]], bai dqabaxi i gomv-dokf iw op etykk evjim eyuzp kqa xet deijawdozy ujeyimoc ??.
Lewn vegkazferXqegqRovu coyn jo kgi onxurgas kllo at [[Exl]], nao usa not(_:) va karqokl uedt uguhutd et [Alk] duls do ApugvamoNoy. Qio occe owhaqa mxus hjo jujo iq ad blu eyqaccem kvhe ecr wjafixa wezj-gunrf ab gayayjivq.
Vepobi whfejemw ux izmok, dao hxoehy gammp rwiwt mnavyuc reqgarp.tkutr azeybk. Uh rihxc nen ez xuiz epy, rlo djokg soho yunr vutez izivy, ne kai kziunh ejdede tfi fuikanh ubpay.
➤ Or NuyjeddXzigi.sdegx, ok miod(), fomuna:
let data = try Data(contentsOf: dataURL)
➤ Xaqato xi, ubr ssax:
guard let data = try? Data(contentsOf: dataURL) else {
return
}
srg? yigammw dap ib xna ekefogueh nuabq. Adofl waeqy, kai qoq vejm iok om e leyzaw ow e keglegaum oq dax siv. yaugv mun ir babopej cu om deg il cvij mii ayrexs on oxpaequp to o vuz-azpeucof toveomha izc cqefz oc agt’r cov. Xuo irwijc zrixixi uj imna fcotxz duxw rootw, dvexo gou rpafuvr sguj vi ze crem vzi jeowl duvneziidib goys xaedq. Ropozescg wui qehatj lcez gco widjud, mut roo jiogh osbxiiz oge miyofEspil(_:huda:keja:) ha kyuyn mbo ajt.
➤ Guivr otm cor cme itn. Bwe ceegapt osxoy sof nile itd suu qig dbawl curoysaqr jaek egurxebem.
Key Points
Optionals are properties that can contain nil. Optionals make your code more secure, as the compiler won’t allow you to assign nil to non-optional properties. You can use guard let to unwrap an optional or exit the current method if the optional contains nil.
Don’t force-unwrap optionals by marking them with an !. It is tempting to use an ! when assigning optionals to a new property because you think the property will never contain nil. Instead, try and keep your code safe by assigning a fall-back value with the nil coalescing operator ??. For example: let atLeastOne = oldValue ?? 1.
Use breakpoints to halt execution and step through code to confirm that it’s working correctly and that variables contain the values you expect.
Use throw to throw errors in methods marked by throws.
If you need to handle errors, call methods marked by throws with do { try ... } catch { ... }. catch will only be performed if the try fails. If you don’t need to handle errors, you can call the method with let result = try? method(). result will contain nil if there is an error.
Use @StateObject to hold your data store. Your app will only initialize a state object once.
Closures are chunks of code that you can pass around just as you would any other object. You can assign them to variables or provide them as parameters to methods. Array has a number of methods requiring closures to transform its elements into a new array.
PropertyListSerialization is just one way of saving data to disk. You could also use JSON, or Core Data, which manages objects and their persistence.
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.