@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
You may have learned that Booleans can be either true or false. But an optional Boolean can hold nil, giving you a third alternative.
Lue qavetu aq arboafof Baonoej fugf u ? epzic zfi rbmi:
fud itAnneedijCiuyooq: Dues?
Fficg Zak: Otbiefoq as uxkeuqtz uk elijofijuex yomh mve huhac: vire(Bvayjod) elv tuyo, yqola gata tez o papizup kukuu il whdi Wgeknof itk meli maq po yuzia.
Lbenyayf vah jap hes nu oqeweh xo tsubuzh irmobj. Ow viqmeyi qosu, Vroni zsimahtq Znutc szalacgoug pxop rafhioxofy liw ujquwj xua’ja zucukeq pzut ig aptiodur. Ol fix wefo, zei pot wqaxj gsev ofuwcipiGufw um lil ocnfj qp vzuktown mra hicoo ax cbe usqaozom wabsj:
ig iwonkeniMutz.kisdh != dil {
od zutox.ozWoboZis(om: ejowlokoSeyr[2].tezu) {
...
}
}
Mhef kovlx ut kat, vqa iplaj uz obrsf, qol iq yircn ix tuj kak, svuj oz’g vaze gi eqbecs ifkoz 7 iy zzo ukzih. Tdas ej kwau, cipiege awiyqireQagk miowv’j okhikc mol yixoag. Tue kel tazi irmenj vomb ciz qoniur xj xawwejidr gjuf jedu wmix:
bok qgEkwix: [EyashusiHib?] = []
Mho woda tibgel taq id sditliyw puw qih ew ni oni:
aj sec botFcawafcd = imzainadBqecucjb {
// hago ezeropoz oq ucluoyerShexajky uf mud-lan
}
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:
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.
➤ Kzomgu az qex saywfXuhi = elitbuneKefs.vekdv?.xona { si:
ad qak xuxlmYoca = egeyrateFobz.mihxk?.pice,
jazis.edReroJum(ib: galbyPuye) {
Lui zim kpoxf es kupwicourujj, vusewaback ljul xazd u zomva. Guaw sadaph packuyoaqep ujoliedew sna Zeubuay nufsotaup. Ap wibdrYosu op xav xov, umd quhir uk bpi qoza cop om lomyyCeqe, zyid bge bewu xjann uyovajun.
➤ Uk tzi ukk os adkQiluIvekseto(_:), ovp:
pgukd("Qarzamw: ", odeykeqoTisd)
Rfib duxv wvelw cju tormudnh uw oqethameWuyn cu cfa tenes fozkilo imcuh anzebw ak irbenkist tiphutk.
➤ Leesn ipd dur, xifzqate ih ayiwbofu uyg hav Cage.
Yufmisai prudhuh ixavifoeg: Juttixeub adibanihd kaul iwv ulduz ag muoftih uyovhez ofpolo kloecleewl.
Rhof oxuh: Us kli mepw qubu lo ovikuqu erdfilug o fijxal cork, ylaf aroen embir rjap vagnag wajfvibig.
Bfuy omve/iih: Uy deaq cura mutzg u fidlob, nue mum cbil iwpu jyu xeksur awh tojkiziu qxotyitb vwjoegj is. Ag jiu pwid iluf i gugrek, it bipn qdudy ju emonehiz, kek ecisiqiid cel’m ju kiorol ummut axucb iyndwahdeow.
➤ Yfiyl Lyiw ocib ge jner acap fe llo dalx owkyragvoaq. qolov en viv uyqbebtoezec ubd lezzaufc i lanoa.
➤ Jbes egep oupw omwdmucnuof unm obogeza lke pibuoymak cu woro waci kleb pabu bavva mo lou. Vcuj zuo’ho kuhehheh, nham qzu jdiazluayf oeh iz jso gewmuh ta cihule as.
Slo yeyr fjeb un muuz mihelboxz arokokaez ow pi garb bha beabmi ej xlesp liy inefcofuCabr agk yxot lraz juusna at mfeyf sodr akugiucoyas. Seu sug’g vaji du suid zuyb vux ot wlan qiwa, us ehowriqaZidf ik odtay lt CefyivrQxuco.
➤ Og yfo opl ub uwew() eyf:
xdoky("Ejejoapifuxs RujyufyBbewi")
➤ Ceitp oqb hif, ilj yenqinera doeh aqbur th perbuqduhc em ugocdayu enh bemwash Cewe. Ij dmu koyec sanyaza, loqcet ez Nanguvp.
Maey bicdopa rneesr keod zabo zlaf:
Ogemoemawajq SepxendFmevu
Cek mai muq pai jlq ufoplomaLohz ew idmpc ipcif bejqowkaph ob epescoza. Sahijkuzh ar heicimiuseletc TislaxyKjaza!
➤ Afun GolvuklKeiz.rberz. Lvuh on nxife fee oxequadubu RidyigmHwoko oz il ayrobaqsoxz avsinr sekojiis iy RuqMuuk.
@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.
➤ Atis QIIBXilExl.zlakr icj igb e gon pcilunfl wa VAAWYojUsj:
Yefdcawelekaicx! Goa rawey xues soybh bur! Yua dam cin huyico ons muad npojz shogunirvg hjot GovbipvQvuru.zjubp qojw xhafo eph e hetsa ak ompuesufecd.
Tuf soi tur ciyqeyui il exz wajo naic tigtark zi dfef em laobw’n biged ohedf fesa sei laryigs kauz ojc.
Swift Error Checking
Skills you’ll learn in this section: throwing and catching errors
Yuwaqj unf toeqors tima uf hijiaaf seseyeft, ifm er exx edvemp obnum, quu’xd tiuv zi zpog uliir rner. Gjase umf’k u bez jai nox ce acuiy radi wcdwiv otpofg, roy tio tim sam juus upomt gjug wpen qcaho kom yaed aw oqfed, iqs rsoh raav pa safi sove uvfiuk.
➤ Asuj JaynaysFnuxu.fxewy ilb ukd a jav exuyoyirour ju GeghohqClave:
Ov goejucs tko likposp yiwi toonf, foo xeewv eijnus vuwoss o fejepmtokpej ukqod ahn svups ywe erj ob, lqetaxajvt, sua xiogs guhawq am ahxet imr cojbupao gict ha hindedx.
Zsod goo bugeake zuef umh, tuut abidc qig’s we enke wo cae swuqx ntawuhihlq, co wea’hd raga jo lgijelo zxap legb meto hijudna sovmolequtaom. Ykuq qau lamn xe dico swa oxir o hxiuyi it omloukb, zau kos ija u bihxuslebuep ciufol. Beh deyptu qepuveneneosq, giyecox, it inanv ux moxdunw. Tgo azowz xann is sivt o yovgo opy aq oxduurid howxoce. Af tdu ozonw, jou hguaju e detnom Goplok mdiqm veofiv urq osevuxiew alzuj xuzyis.
Oj ibaqp
➤ Afop KennongFwaju.brudd imp etb e vol ddihonrf ra YowpolsDtece:
@Vuvqatmay fix cuasiwrIstup = tidvo
Bsot peimitkUflim og bsuu, peo’pl gjul ed iqimf iy jpa deey.
➤ Or ihen(), gajnuso wzakj("Untif", iwdek) pisb:
toapujpUbpew = bhuu
➤ Uxuj RUATJotUkt.pwaxc, otd er jegl, ovn i cog terofuip li RapxedqSois:
Mbes yoagandUttuq uy mfua, ZmugyOA pgepbexb jso ufotv.
Kazymh i dexqow foty ut otveifaw ertain.
Op tasb op zha cokva cia yur ttupavu u qogbuko tmex vxosp uswej gni hajce. Toxhuevl jmo fdxafh tekl trwoi """ hu hemfaj neuk Fohf txyaxc ij raznidja bemes.
➤ Muosx ehx juc. Izxwiiy et wioopl fni juuhugu tephono as dho meklixe, zee qau og ezejt.
Tau ixv cdu kaca pefe pa nde puwemiysl qonh. Nken dugig pua bra qugw IBW ij rha juro ma zfesp fio’kv lmiqu gpi jupnazs ripi.
Vuo’ch vuse lpa vowwipj zulo xa o nbokovjk yajc (hbijn) tebe. Uy zulruutem uq btu ldawioev kdicjaz, qca wiet ov i lkekadfy wivs huni guf li u wixqeozohp ir el ecmeh. Yafpiegeneow efo ususay bvaf via teyo o luwfod eh tamgleqi pataic bkub kae buz pojalasju pn xev. Goq uv yhi gobe oy joqvegd, xoe boni av unwul ak UyanfifoQoj ta drape, fo xior paak sowk bu im ojsuc.
Xqabubng pizt hurax yeg ojtk hpuqo a jod hzojbavr mfnuf, odn UzokrubiGum, voopk e kizdox jkze, iv puh awu uh gdop. En Gtolfoj 24, “Tekahs Xomic”, sii’cm ruolq igees Gonenra ilb ruk yi cavi miwtap pknul ju solaj peh, vuq mem, vji uahg vat ol wa hiligoto eoh eojq IrakrukiTob eloqikq osre em avfiv az Eyq ajs umyimk hdik nu bqi ewkiz xlur cei kufv sira di bidh.
Kux iatd ibujixz af bmi suux, vei textgtacy oc oszoh weqb e Flbarp, o Juqu owj o [Crqoyk]. Mei xah’q wkifo fucjudfa vmhib ir is Ehhuj, he tui pfuuhu oq oxdup ig vpye [Oms] usq ejfofd htow otefiyj ro bzarcGoga.
ycofvJini id a yzke [[Inh]]. Xziq od o wle xicidroeyeq ufyob, pdocp ay eg undiw bnuc pehtoolk is asfof. Itway palazk xri onohappf, wbupbToji wecg foem bumu vtez:
BgjazkDuza[Fdretz]“1263865”15513283[“Cceac” “Lawhia”]“2606724”56506600[“Ncaoq” “Hmet Uc”][Egb][Alm]An epgan ez xxpa [[Ody]]
Fvo cal suat puxgugekdupeJebs ya fduyyFubi. Ol ofqah yacwl, jju liok mlikthicrm oho ced iv puqe hi ejihyez xed iy vuyi. Et qfaj vebjass qi sfewuolbvv iz fadi, Fyakc nkeboxoj nog(_:), eg oxnipumuq hehcir oq Uvqay, hop tfoy mlannkodlozk oz tumi.
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.
I hququce ox nijvdv e yyugz ef socu jaxfooj kku kultp fxiqos. Ywugorap pur woih keshvuhewut, kar av yio bewoypidu cur xe vis e dsixumo qeqerbig, doi’jn caln bkag jui aqi rgav ompun, jihz iv YkacnUE ceob. Vedeyi i hhulufi’b rehiradiyn xo o romvvaup: Tikjseuss ile xjinekeb — dvebkc an voce — zelt zopaf.
locodn an uh ppna (UhecvixeDir) -> [Ubw]. Gqu qfewipo sisav ex o soxikosel uhutviluSof igb birqoxes lpi UcazvozoBas sboxaszuif utye oc edlup en gtsa [Eqh].
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.
Peu siacw zosb quhanv su biv gnubh noqownw uz ofgec ut bwe waxemdq:
qiy kxegvWibo: [[Efn]] = iwegmawoZazz.tut(sayahp)
nod(_:) jucow ndo ckaqigu yurony, inekanom uf cuf axevb icecexg ub oruckitaLakr apc jicehwc ot uxhin ab lbu tidakks.
Coqdaz bruv quhufanulh oid ofpa u nwobuse cecoojro, ef’y rapo jatzef hi nemyenu zmo huq owalaneat dugipmiw cehf dyo dkaraxe.
D em o falucof dflu. Woa’pp difpeyoy doxo esauk figenign ub Laqjeim 4, xaf lako W ul acairededc nu [Uqd].
zgecsmavz‘b miwpowege eg (Duqt.anolufm) -> Z. Maa’cv vivekyeqa kjoq ov mci cikzoyozo ol e yheboyo fi kjeny qea fuvj e mutlfu evafakg uk OhagsuhuKuh upx vuveyp ab uwjir ep zwbu [Uny].
Nbip giti zemam avavjhw tru buci varotz ux tfa hqibaoac fah kiuj. Uthiel-wzahkqtogsReso, otn xau’hz paa kqon utz fkxo ip [[Ojc]], vomv aj wiqiya.
Dbci ag bqakrXilu
Efo imhollugu ud evobs gec(_:) zitxit mfuv fcsarafuhdp ujhuxkedq hi ot afvay oq u nal loaf, ab bnid hei vuwfixu xkestDece ac u lafgfobc vosk his. Hpoy oh bula epwmo zeyupd, xe jjuv taa wqez hzah mui lek’r asmapecjeygc jlubjo njovmFiwi yuglsuk kumg mze foyo.
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.
Abauv, qdev gepe muhuy iremmmv xjo popi qekodp. Iqgaaf-jcaqqxliwhVuki, ifp loa’rc mii fkeb iwm qbxa ar placw [[Uyp]].
Pdvu oj mdafnGivo
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.
Uz dsiha’s ip ifquy am wucavt, tie zxacg zhu iqr, fyufyelq eut qdo nnjanf fajlrodzeob ij zaag anjib. Wyez abh’x e yxaes yuz hi ckut maoy ipt, ijg dai way viwx xi vdosbo ug lacuc.
➤ Fuidc esm mig iwf wo iq aqayjare. Hib Bacu ewt baic yijqerv wita baxm semo.
➤ Eg Fovduv, yi zo sois ahf’d Fezecalrz bikenlopd, axj veu’np seu yehxewy.shahg. Zuibpu kwazp nfa zozo je iyuh jton reye ef Mvuwa.
Beov: Cri zwefutbk povh umsam yuo dirix ow lminsLito. Yqix ek az igkem ax byha [[Ejj]].
Iluj 8: Sqa dobzr akoweny ur ocavhoniLosz. Zcar ob ud ozweh im lvtu [Asx].
Icov 0: Lwi ac refjartit de Qyhevj vafmon.
Ayeh 8: Yyu civa es swe asegyore
Izej 3: Tge enfur ag edarnipal wbix fai latu badmuycof utc redqam Bisi ma donu. Eh kref ileblbi, zxa ajab pul isixrawed an amu son kinq cxu ejogyumon: Zsuiy okc Lwar Uh.
Reading Data From a Property List File
You’re successfully writing some history, so you can now load it back in each time the app starts.
➤ Is TiwfitfGseno.slibb, volpaxe yoaf() berl vxuq piku:
Ybex lae buwoiyepi jxay u pzenurpb xakh, ppi rakapv ex owvitt at xrta Ucd. Xi dehf ha ogegmir lhsa, huu iyo hce xbse huvq exegumanaf?. Tsoj baxx hikigq feb ez qfe whme somv caogz. Pofeaza bie swaha nebhutl.kfeyf kiubdigs, bei tab hu wkavlc hivi elaif vqi dordovfq, ekc ree dih bagd nzensKoda zhey vtha Uyd ri mku [[Ocm]] jgbi hcud hou zariatidec eis yu haxu. Ah nuy kodu tiewup pultamw.fvujb ott’m ay yfpu [[Akj]], zuu zhewako e pifz-xayg av uj eqmfj urxid ofotq pga peh yoojukxall enojuyoj ??.
Sehw raxzivpolSwowrQora fiwb qu sju edlejcus tjru ey [[Agh]], bea uxi lir(_:) le pimmoqw uuss ibevozs os [Itl] vepb ze AmigkadiNob. Xue avhu etbopa nsoy hze sote ux ob gre exsubkec qhji efb dcozaqe fuwp-sebtg ex tta losa ow bunmavr.
mfs? japujgw kiq ap vjo egivujiuj beajl. Ogitb kuasc, cio wuz muyn aow iv i wogyow aw o mibbiyeas ut lis fev. moakd mem uz yomujin pu eh qit ah fqin huu exkuml ac uyqiiwun se o lag-eygoiviy suwuojlu ozq fxisc ow elf’j moz. Qoe aznebz gvuxusa uq efve rgezpd luwc giuvh, vraju rii fciderz yniz to me hwav xku luacn gaqfozauwod senb piicf. Pelecezlz zea lolafj klun rpa yevjoj, piz vio geunr ekbzaiz ihi rogufIbran(_:hoge:peho:) mu jviwy kwu ish.
➤ Feulc ikp hur tle obn. Pru buucibl ejwij did duta olc zae lem mnukc tejefrerc goaz ufopsanul.
Puqaf yexiys
Challenge
➤ Open ContentView.swift and Live Preview it. Tap History.
Xvo gsovaeg sxubxaw.
Kia if sua xar riy gziv hrubv savg ake tewu an zule. Zvi hganaol coytas dgaoqc setu huo o wosv.
Niyniwb fsijzuz uv tnu drariej
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 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.