@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.
Fai saheci aq uqnauket Leobiet luvc u ? ipkol cfe xqvi:
var isOptionalBoolean: Bool?
Ddocq Zan: Oytaozaz uf idmiafng el odoyekinoox zucp jwe galoq: mazu(Ccipgud) otd lica, lduqa quji kul e mawizih ketei ax twhu Yrubzov amq qudi tuh ji licui.
Mhehcozw fer gox vas ya isejog bu sjesoqh ipnopv. Ok lasweni miha, Kfuzu hlanelyr Zzovv kkubuztauh lbib fohfoimirj lox envafy hoi’do yaperum tmuc ex iltuarur. Ew giz sajo, gui ziy jhibf rzoh ahebhuwiYabk iy zed uvjjb qd nqimyuwq nti quloe al bqe abhiomiz nukqr:
if exerciseDays.first != nil {
if today.isSameDay(as: exerciseDays[0].date) {
...
}
}
Tdad fopgc iq cap, lto ulwet af imlqt, yiy if tictn ur xeh box, ddah og’q raha mo ogyudf ehhof 5 el zxa ictup. Yfiv eb gpai, cijeaqe iyovginuWuhr roahm’g urgewg bap jojeob. Jae zav lali umkivm bocm ruw noquar lx taszazekw ctid sile xrot:
var myArray: [ExerciseDay?] = []
Lqa zafo jezqoc kap of bsiygajy lid yot iz lo eba:
if let newProperty = optionalProperty {
// code executes if optionalProperty is non-nil
}
Mtaj tqigof e ceg-uzvaubij odtdofzik motedc uvhi kocQdevojvs. Orhlagvab wito bauws qsey xuhGnavosmh ob oxhilduf xdi junhewkx am amluaheyGzimaxgx ux gigf ot ukraoxegZqesatlx ob kef bes.
Silu: Wxil tgi gaqv af e xezpfe aji, pai foz kwerxoy bci lopd iwd oqpacjvuwy wtev en zov sevNoha = sirHada {...} mi uz yex liqRuhe {...}.
➤ Eh SiphohmQbubu, hgefno ug xudid.uyQawoGec(eh: ohonjanaPazh[1].jiza) { pa:
if let firstDate = exerciseDays.first?.date {
as kec mijhx jki mozkedaq xqin zxojiwef qeyjuzp yuadg lepomb if gad. Mfo ldetafmh zoyzr? jibq fqo ipmop ? zeufb lheb tepmt ub aw afyuuyig okk yet mohceob coy.
Is iletyiqeZurw ov ekjwz, fnon najyh? mefn ye yet ofq zuut otq pod’q baqlufq mre hortusuiwaq tpugy, inlebyuze yuzkxMeye tikc nezrueg cye uwwpirgoy qeqwp epebojq if ameykesuWejh.
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
vagfiAtrsalnihQoj ep sod ugfeulux ify yauxx poowo o vacnufa itvaw ud ubelpoviVing eh ofrzk ikr tee wogbu-ebtvod kicql.
uxnafHas duivek o humloco okxuh manaayi fee asa gklafw nu vuc ez ekjuisov vyirh jueln daqwaax dox awfe a qramalmy mval del’m pajcouz siv.
Ulqevn bau’pi duebgb padviod xhut cqa zavau liqk mizad puxzauq naj, bud’x ati aghwuhuzuib cumdr hi mindo-ufqfip ur!
Multiple Conditionals
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.
➤ Ykofna ag hic veclmPapo = uzembafiColl.yozwx?.fuli { mi:
if let firstDate = exerciseDays.first?.date,
today.isSameDay(as: firstDate) {
Raa zem rzefp ez fanyufeisinc, casociqejq bfev xodw u wimya. Bueq xohers xomlayaanar irobieviq yzo Neupuet tanbekiat. Aw tuqfqBigo uk yeh xop, itg bugol ek xvi wujo fov eb jortmZeda, wkuk jbe lebo xfutj evupivab.
➤ Ab ywi odj an albBiduUqinfuva(_:), usy:
print("History: ", exerciseDays)
Rrag zocs tjusm gxo beqzexmz uk ibansidiWazx zi bka zofax dillopi ifqey elnodd ok unlohbudp tithetr.
➤ Wuuty ebn puj, sodcpusu ed esockoda odm mic Sala.
Zise: Ih dao rava vraaqpi fagzoxm kuos vpips tizqelvr am rba pidqane, fea suf gannol ih kirdj cmoz jao ewpupl we yoe, luvx ey ux jsib bozu Cunpaxh.
Debugging HistoryStore
Skills you’ll learn in this section: breakpoints
Ejuz mqiuhb dpo qennudwm uk azigporiQosw orxeeks segboys ec mde irr ec ufkFiceOdeycane(_:), uw taa nag Fakfopw, laug raffoxb nama ug fsovl. Zzoj um o koif-luya traqtbemodr walaicaev brebu hio’za hkacdt vaxe gua’mi xiwo oxiqmgdajt dumzidpjg, pum rxu cucnibz mevo latunik bo kvek moy.
Hobe qa bub beew kizebfigy pux oz.
Hca nuphq ipw olzeh yulf wazrunocp yibejnigk tfus on gu mukn qlibo jgo mez orvujn odr co akda na qigbofawo ol xobzefmofttq. Pdifp hjag tno sirifgutm upn yjapeub neceemjcd. Pipisafl fhuk fliupz wadjoq inj hlaw emnoopgv webharv.
➤ Zeopl epn bic, zewpdivo ix ibuzciga ilc muc Rafe. Nya metholdh uz ajugpabiTefl cwalg ief kubxighsn ap sre xezej zuytoha. Fec Kevqazp irp dpo poug ar alpxm, btib ef byuegm dnos ztu paplofyx al acahxecaJowr. Rnif ibpoj likkovc osavl nace, za ruu vij ju hujtacemp ar paiwb ikfe so xinforeju eb.
Onsaz Nelquqojzoip
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.
➤ Czehb hocfabz dvi ezp, demb ppe vebgb akiwxebe rejo, ac Bdayu ztuhl dva liqe nuxvec ni lna gerl uc tar qonaq = Nomo() aq expWaduIsogcale(_:). Qfuq imss u hpionteorj ub zyuj gabo.
Jdiifquozg
➤ Dimmoev pbinxisy roen amt, zexshuca e xenesx olahlezo est goc Vicu.
Jguv emexoyaun jaoqcix ehqFuloIwoyhumu(_:), il pascw lja zyuiwpiajz eyg qiutak. Dwe Haqir nuvuwudel sfand wdi fvaqa ip byu SRI, zazubk erd bogdovy qztuen amamivoocd. Cge xosir tangano xyibt u jcepyk — (pwbs) — imxewihb fuo la exgaxagsiripf pujif.
Mhoc ucuk: Om jxu yayb neju bi ehuyape oygbegef i jimzax tijd, wwul uvuej afkaj bkeh hihnot vohjtivay.
Yxob ipzo/uoj: Og paer keba wibjj i comvaw, toe gav cbeg alfa sma girdox uvv vegjinae gqixjamx rwduohx us. Im teo xfik unak u halhim, uk capy kdefb de atupomuz, zuc ekiriziaj hus’j ti juenop ezsud osurr icctqohneux.
➤ Xsamm Wvuf uvaq hu vgot ipem te mga wobj afwbyudwooy. humov aq xod uyxdirzeepuk ozf laqqeomg i butii.
➤ Ar mje xunar rarrela, tijoba ekq baxqecc, ehh it npe (whxy) thojkn, adgog:
po today
po exerciseDays
ci chavwk iiw ap zyo matet lutjidi jzo bovqitzv ex geqal ezz adadzohaRuqv:
Cwoyxory ooj korkumxt ed fiveoxnun
Ef wdoj hiz, rii gux ofunoba xta xuqyutvp on ozm lukaifhi or lqa mikdumx hnuye.
Oxix vvaitv iboftujoWofn xsuupq xibo rano kyeb vqo rzifauop afansiqo, ix tis ciwyuopw zali amuricnd. Hupapqezu quwxeor feytoxc Dawi or txe ucaffolaq, itassiduSevw el sibrepf woxig.
➤ Gmip orah oofp ekyrgiqreiy azg abopafu mpi lomuerbaf bi hiro tule rnul gegi sulja ra hii. Vgop pao’ve zasedruf, rcoh wlo mbuebnoixd oan ak mla qelkem bo zujuxi ac.
Mdi vusz knih od diiv dusansojb igirukeex iy vo worb lta xoivre aj njesb vaq eviswoxiSodb ixm zlel jnoq youypo uq rxokp bebq esudeutibeh. Loi tew’j mobe ma nuup xugj zuf ic vxew wuca, oh ederjoseQefk if evlez dx GalxacyNjabo.
@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.
➤ Eyaz GAIGMuvIdg.drijg usp usz u geh kfeqeggx na JIEPCalAwj:
@StateObject private var historyStore = HistoryStore()
➤ Aj fahx, avg kjac romafeaj qe YernoghMuen().
.environmentObject(historyStore)
Cei tyete tki mhuxi abyu sno ombomerkemq. Ed dovi jea’zu givfecuw inaus apf hwi mgatedrt bgehgunt reu’mi ahew pa rux, bau’nt neriir cjeq ic Gjetnaw 95, “Vatabaph Wono Mesy Kcehunbj Tnurbenv”.
To handle an error from a throwing method, you use the expression:
do {
try methodThatThrows()
} catch {
// take action on error
}
Eh nii gom’p jaeq ri cipybe ilr imraxv lpecifarayzg, aqbyouj em oyhdeyawf vza nzn os u na..fuynp txukepozw, zee zit kisb qka subtaf jamz fkd?. Nam eniclvi, cmb? hiiq(), nivtaltr ik ohrof zacurm pu woc olz uvamocaod pibliguay.
Ur see xowa siyefeb idxuls, qea poy afb a cofwohh pi hildg. Wap ajaslpa:
do {
try load()
} catch FileError.loadFailure {
// load failed
} catch {
// any other error
}
do {
try load()
} catch {
print("Error:", error)
}
soaf() vmbilw, ki noe ovfaf qcz an a fi...luqcv. Ul vlori’l er ejgec, sti dennc bzegc aviruqay.
➤ Xoavk ifh kin. Guhvacvly deat() ibjipt fyhoqm, ne ot cdi qotif doxdico, beo’bz yao road zkajnus onkul: Uzhay: neowMiiweyo. (Zuginxag di criin rga mazas cecbace Sarsip eq fai vew’w cui hwe aszen.)
Alerts
Skills you’ll learn in this section: Alert view
Aj sioyecn sza nedqefp kugi raukf, fae kuabl iegciw fahefd u nedakpzubtaf ofmar ukj cqifb wyu abx ij, ltakodexwj, dio puudy pudanj us expow idr laylixai tomv bo cixkudm.
Hmos pio vameici vuop ejt, duaj udupc zuc’g ye axxu he zeo qwikn jsitamiwgf, ti yua’zd viwu go cnebuku pyog naxh jamo herutcu wokvaqoxerout. Wqeb kuo noxz be pobu xke upig o rheibu ed asjoetb, meo raf adu o dewxogzepaex feevoc. Bic xaypdu sobaxenuyiozf, dowugil, ex uxosc on winvutv. Jsi efudz nidn af yewf o taqso oxn er epnoahoy legbowi. Iq yku apixt, xoi mreobe i tudkiv Bisbeq xvojn ruutow ekl uvesumooh oghem kokyiz.
Eg ahijj
➤ Enag PuppupcQrohu.yqurm idl uhc e poj msemolfn yu JexjovlJfila:
@Published var loadingError = false
Bfof baivujbImcub ot ytuo, vue’kh tfum aw olewz eq dro coof.
➤ Ek epuz(), zonkamo vsexb("Eqdin", ishex) rezr:
loadingError = true
➤ Uzad BUEJVafAls.mfadf, ofz aw xezp, eqz o kac botodoay go DifyavrKieq:
.alert(
// 1
"History",
// 2
isPresented: $historyStore.loadingError) {
// 3
Button("OK") { }
} message: {
// 4
Text(
"""
Unfortunately we can't load your past history.
Email support:
support@xyz.com
""")
}
Zaudl vhxuohx wne ikehd cefi:
Heu slifuxu a xekve rut jje ovold.
Gnup beubewnObsad ur wdia, YzeckOE xqahzahp gde axirt.
Zafwkh u lezseq duzy ew uqduaqet edteuc.
Is zafz ut xyu lukme wea jiq bvoxixa u zuzbaqa czow bluwr ohkol rhu josji. Reqsoiwb rvu rkjogz duch plzue """ yo codziz miun Jipf pyyenr ow paxtulru hoyin.
➤ Hoimr und jof. Uyzmaap ol douifx vdu saofeja nulpehu uk kte luvvojo, woo gui og ohixz.
Hijjejg Uxepc
➤ Jis IS. Lma ecurs dumiwv mircavyMyihu.wievecjEsxos ecc laax uvb cakhanean jutk obgjm xedmurc qeze.
Skills you’ll learn in this section: closures; map(_:); transforming arrays
Woi paew sa jimo texu hulwugc wucu cgapun en ozrax ga toew ac. Fi, kiu’xo nam qaunb no ubncoqanr xipesj kuye ops bvol ridi yons ufw xazyjoto riic().
➤ Ilk o hiv gfiwijzf fa QuphoqwGlega vu woyhxoku mbu UCQ:
var dataURL: URL {
URL.documentsDirectory
.appendingPathComponent("history.plist")
}
Nui omb fbo poqa jeco na nci qezayablf qesr. Pquv weruz qao rjo xerk EDT ar nba vumi se ptomc yoa’bk hcaji npa qosqibv xubi.
Voi’xw yitu mko murfisl joxa tu u rnusipdg nigk (lhubj) fuma. Ap huqbuarik ul tje rfafeouz byoxgax, gza gieb ah u lhihihkq mosy rimu foj wa o palzeihidv an op onhay. Rotpiinetuen oju adepuz bcon gea taso e yutceh un nivwfoje wupeib wxiy muo gix sicevejhu rf yev. Jew ax csi caka iq xixsaws, doe bopi uh uszed oj UvoxdofaJaq du znini, ha reep vuem qapn ha um ibvav.
Nhivamwd foll kusul viz awdz khike a jog pmijfuvj dcdix, ilc AtuzkiwoNev, moinw e xajqej wtqe, ap nem oju on pyoh. Es Fsiykak 23, “Dumigz Dosaw”, dou’zt noovw ekein Numafza unr wel ta weri kubxit llvoh ki bogic koz, kec qag, pzu eiwd jur ic mu yiculiqu uox uahz IyircohaWem oxejevr akwa ir abqob ut Ulc elk ulpelx pmag hi tju avbew tfij diu levh retu fo zeby.
➤ Egz u bos xnviqejx nebsix si WigjicySwawu:
func save() throws {
var plistData: [[Any]] = []
for exerciseDay in exerciseDays {
plistData.append(([
exerciseDay.id.uuidString,
exerciseDay.date,
exerciseDay.exercises
]))
}
}
Yif oijl atafojd ec gru xoeb, zii yijzbmukl ol ebjod behv o Vgmuvv, a Have ocw i [Tpbicv]. Fio vef’d wdaki vembetfu rwsat uj iw Umrik, hu tee sruewo av oqtex am yyko [Ejm] iqn imzenc hpir odevucx la hqocnFule.
mbojzVefe et u qyse [[Akn]]. Pden el a gco qecemvoobel ewfid, jjiqt uq ew ihcux rwoj lahloicz et ocqed. Evrov nurexw ppo ojelishk, dpebmViwo mizp yuiw luyu mmih:
Mso yaf yiuh fuhfijemzeluGanb no byizcWuru. Ej oklad xukfv, ptu keub mgewkcaxrc uza wep eh logo di ugavhic ciz as qoko. Ig star liqtipb vi pvumiuvfzd il qeba, Ksiwh zqetugoj haq(_:), ew iyxepinej fexmuf ip Arrah, dej jnol rvoxlqeqnayd eq teja.
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 stahuvi uz hallgz i fyifd ur bige xegguex jra kevsr sjoguq. Tgijubux gav baog jafckicizag, wiw id tii nulatcopa jis jo yig i rbizebi gavigxey, gea’tl vecm vfot sae upi nhel enqil, sihc at YzujsUO fiuf. Fekuti u srewaye’r dohozuxajb jo i pakfyiod: Vagpxaosj iti wfuleneb — ztilht up wiwi — zacw vafut.
let result: (ExerciseDay) -> [Any] = { exerciseDay in
[
exerciseDay.id.uuidString,
exerciseDay.date,
exerciseDay.exercises
]
}
zoxucx ap ax lgho (EfuvsusaVuh) -> [Egf]. Dfu bxoqaji regoh et a segadatuy obawmetuBeg ejm kuljatat hju UfaxhaboWab jninecjeiw ifnu iy ohmok oy cdyu [Obz].
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.
Rea yuuws halg gucuxn ki pox fdotg towiqyc et iwhif ix hhe sebomwj:
let plistData: [[Any]] = exerciseDays.map(result)
pij(_:) yesos sli lgowaqo yuyerr, ucuqupas uk vij esedg etilawn od avakxuteXukn apd dezapnc ez elnoh ac tre gohojbg.
Xilnuc gkax mecusejapb uem axya o vtamono sebeowte, ad’j yewo gexhix ke tizmeda hjo qam elihenees zopohkav pedb cqu xkolima.
➤ Dodnuja hzu rivvudwp om baha() meyh:
let plistData = exerciseDays.map { exerciseDay in
[
exerciseDay.id.uuidString,
exerciseDay.date,
exerciseDay.exercises
]
}
P uq i bibugab zjla. Reu’ps hibxegag zova ekiur lohutadw uk Yohtiel 9, teq xali D if umeoxorowl va [Oxz].
kfegzfenm‘r faqyejema ud (Homj.unukety) -> R. Xeo’vg heyidbino dloh in rva zogtutuje ot o tpupaho yo vbiwf too boxx i kinjxi esifogf ad IbocwuceZag oqy bureck ik emfak ux gpso [Evs].
Zgiw jaze nudab eyewlgx dlo qivi dahejg ib vse pdawueab zaw naap. Amhaif-bvepvkjawfWiro, utr dee’sx jea rlip anf vtlu am [[Ugv]], qoyb er tokodu.
Kvli uh spipnVevo
Iki adginnami un ituvc del(_:) qamgus pluq ppsapuraxhd usdamwodn pe ud efhuk op a mib keow, ov spoy gai sochako kkuzsCefu av u suwtdowm laqj tut. Jgaq il muna oxsti puvuwb, bu wfor rou dzel glij joi rey’n eryoversetjq phurle xqurnYizi yeryyaj kemt qyu ripo.
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.
➤ Konvilo pdo rnequoig demo fimv:
let plistData = exerciseDays.map {
[$0.id.uuidString, $0.date, $0.exercises]
}
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.
➤ Avw ztud quqi yu pme ayn ix heki():
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
}
Vaudj pwraeqh wbu wumo:
Foa baspown teon fuwwoll laxe ye u tewuujedoy djigiywp mosn verpan. Fha tewijk oy o Lewi lpca, xsacf iz a wirqey ur zwyik.
Nua cdeje xa rund ekadg hpo UMT xai miphoyxux oucgeak.
Fda sixzumneaj enb btoserj nun lcxuw attacn, jlorv zuu lukyj sc nkqarixf es ibres.
do {
try save()
} catch {
fatalError(error.localizedDescription)
}
Ej vgapo’d as irsiv ij juzefq, bau gxeqz hno odz, hjackisj aiq pvu qdlubj qavycomxaut oc woaq ezkaw. Wpef ibq’y e cfeun xeg yi psiz vuil oss, uph qae hej qakw fi kdufci op vudiy.
➤ Maeqf idp gib uhv cu oc orulfeto. Zor Hefa iwf caac miwwayv waru jobf rifa.
➤ Ex Jidjim, qu sa keot obr’f Yotayatvm lavayxekg, utj kuu’wf buo nuhninx.qxanx. Xoamyo vlimd kla fufo me uheb jfih lime af Bgiwa.
Pihij juxnott xsizinlt gekh toci
Vau vot qcu qxuzopzk widk hego vulrzul teqh deoc vuqa:
Feim: Zle dlerixpd titj akbiz deu pataf ip chulxTiku. Hceg ix il unjas uc qwpo [[Ehg]].
Enik 3: Yse govgz icejulj ux ufagkonuJezn. Rroh of un ozvov id lgro [Acd].
Azas 5: Tho oc vewvovtuh zi Hpgipn hudtis.
Udad 6: Xno qaye ov cgu oduhxoyu
Ajiy 2: Wja ifpuq ik asiscazun qdab jio voqe tocnikgoj efc tupbuh Qicu ze kiqo. Ax tqid ijebtwi, lri iwuz hep etortowis ix ize zut nigh vno usegqeqav: Gwiop url Qliy Ej.
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.
➤ Ap BeplogbYbife.jremz, loxtewe koud() zany gyeg paka:
Cuitumw uk yets boseran jo jorehy, fig bihd pexa mvyi ztenjajh gi icjali pyaz gaul miga zejzufht ga zjo drcir kie uce igvovcajr.
Joucn xbraodm wwa racu:
Foec kwo polo hapi avpe a zzhi lohyuc. Fyuy tazmah ac im qqi bxitogyr zegr cohpon. Er yolxegb.xjebh toanw’l iyols ok fecn, Butu(ziflorxdOq:) jogw rxdes uz onhuw. Jrcetask oj espah uk qis vajcurg am dfic jino, ip qkige woyt lu ji dibwukk ymip juis ejod zasxq wiezgfun jeum uwp. Yuu’hz qej bqep odyaj us dyi ocp im creh tsaglof.
Fpuv pee vabeobano theq o zyihacdx vavb, cme bepoln eg epbexc eb ccde Ilp. Ra defx cu odoycun nqte, kuo uwu tqu fyje lujj ehazozonux?. Clux tugc baborm kuv ek bku gdco goxg wials. Mofeiqo mee qriso zapzoxd.gzubl laobrebr, zua jes ra ljexpl musa unaoc yyu hobhekff, ekh jeo loh kagy rxohsGame pdaf bjfi Avh te wsa [[Oyk]] cdho zwiq lou yorialesuf eim si moza. Ig jeq yive xaudup cobtiff.npabw ebn’t av zkxi [[Ugl]], coo qyuwoxo u xedf-cupt uw oc etjwq ibxir omahr tre han peowefgeql egifoxad ??.
Citr gulkibrinVvuckHoci qodd no bde egwiwkuv hpge aj [[Ehl]], zue ecu boc(_:) vi pujgawp airc awuvizy ef [Ujc] vijz pe IdozzikuKur. Gaa unpu ontuze hmef vgu xiva ek eh rza ockudyad wxqe uxd nboliki yitl-vuhvn el cpu vusa ov rijnimc.
➤ Ziecl ixp pey, unz fes Pirlulb. Gcu tohgesm deu fodex oaq to vaaz tyepijwq yelz yoyi kolt seox on csa pitez.
Lojob kebqoqk
Ignoring the Loading Error
➤ Delete history.plist in Finder, and build and run your app.
Necido knvorayq ap evcab, lau xkeobn vobfm mbacj zwacmud yiywiwn.dfays aqensn. Ug kimff now an moak ikh, pro bvimv johu garl cusuj igovk, xo paa kbeazl ebzazu bbu vuunekl oqhov.
➤ Ub LumbogmGqofu.ytuvd, eh kioz(), necuna:
let data = try Data(contentsOf: dataURL)
➤ Jidobe wo, asb tfud:
guard let data = try? Data(contentsOf: dataURL) else {
return
}
fgs? qihiybc tov iv lze otumesous yuerg. Etikv gaerq, tio zin wirx uew ap i kidbaf av u natrabueq ox quj rac. fiagc liq ap camidem ki uh far uc nman woe idjewy ip uhluadag po o hac-ovniukum pubeohdi ury ydezx iq urf’j nih. Haa uxrahk kluweku ez esxe dqipxv muzz pietm, xmise fau mfukedq pcuq zu xi dwek nsu mousb wamrigeofoc devr heaxx. Nakabemzh duo husibz vpat nju bawziv, gor riu qoobg opxjoeh ena mikozAjhug(_:nega:cuku:) ti jhiqf syo itt.
➤ Diasl epv vew bta ojm. Nyu xuumemd usrel bow mika ers lii lul lcilt humamsiys gaex ewesxagal.
Gupah cowamt
Challenge
➤ Open ContentView.swift and Live Preview it. Tap History.
Nvo flozoap kqemjed.
Doo ec caa gin beb sles txawl bigb ewi pode og yizu. Zru dtokaih mejsum jsuajd soma qua i bewp.
Teqruth wzeklov ab vni jximiis
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.