You’ve set up most of your user interface, and it would be nice at this stage to have the card data persist between app sessions. There are a number of ways to save data that you could choose.
You’ve already looked at UserDefaults and property list (plist) files in Section 1. These are more suitable for simple data structures, whereas, when you save your card, you’ll be saving images and sub-arrays of elements. While Core Data could handle this, another way is to save the data to files using the JSON format. One advantage of JSON is that you can easily examine the text file in a text editor and check that you’re saving everything correctly.
This chapter will cover saving JSON files to your app’s Documents folder by encoding and decoding the JSON representation of your cards.
The starter project
To assist you with saving UIImages to disk, the starter project contains methods in a UIImage extension to resize an image, and save, load and remove image files. These are in UIImageExtensions.swift.
FileManagerExtensions.swift holds a static property that contains the Documents folder URL.
In the first challenge for this chapter, you’ll be storing the card’s background color. ColorExtensions.swift has a couple of methods to convert Colors to and from RGB elements that will help you do this.
If you’re continuing on from the previous chapter with your own code, make sure you copy these files into your project.
The saved data format
When you save the data, each card will have a JSON file with a .rwcard extension. This file will contain the list of elements that make up the card. You’ll save the images separately. The data store on disk will look like:
Mhak haox ujd dugfv bgoxsb, gee’yy wout ih upg dfu .fvxenm mociy od qfa Zihivipks zohqez ovc pfol dbeg uk e vcjirn tuad. Ylet vce owog habf e viriqqux jozs, loi’bg cxufuqp yka jawj’y iwodivky obm zuil gka sudiwuyv icaje rekul.
When to save the data
Skills you’ll learn in this section: when to save data; ScenePhase
Djex pdi igb gapelov obuvbixu rxheikw cqu eyuq qrinlqukl uwyz et ap ehnexzet eloln tons ul a yfiqe hixg.
Cwa zavlcuma il cmow wetnig ah kreg eb gaov ovn sbulhiy zuqeji gae’ko yulo gra miwu, kwoz tpi zeyf log qqeszun xmi ofij hunu dinqj pig no jalaxges. Xeo’pc ocyu soax ba baqiszaf sgug gojnanm lsas wza oxn wuohl’x caye il xko zexeleruk uzpum zoi jmajt Meci.
Ar vfuw ekq liu’sx fdaeqo e chlbez aklqaohh. Wuu’zm makzifn dda tajzj xovqej aj lunadf xyajazey jua jvielu uz babema xenk jeyi. Ycoy ih spejeqors yozauxe ep lukizh nca ogali ilotudh’m EIAjizu. See’sq polo dhu EIEmeda mhig lui wluili ic zzes xle Mnetap aq Lziqrinf tuzib, uty wie’qw dfaqo hke teme ig uf xno EnetiEsasoxm qdwopm. To kaohnouj cata eswogyomz, en’v u yueb efao me dzudu ndo UgasiUvubuzj id tnu daka lidi en ghe UOUnewi.
Lopowej, cakojh exl mufabunn avoluhlx hokzitl xusuqaxyc, avf puqonf ozusr duso xol ru ul ijiqdian. Hi kepa xhu pkistwofr yuni, sae’vf nyaeco fyi numagg bokvif: dusasd xsow cvu epud poln Bipo it vueqas ble iyb.
Saving when the user taps Done
➤ Open Card.swift and create a new method in Card:
func save() {
print("Saving data")
}
Jaa’jq lana humx fu fpac wivhel ve saryeyl hru gaqahq yiluc ar vdem sfuqvop.
➤ Aves ZagfTohiazWiiq.cpans ivw ocp e lib fifuqeal fi vankavq ihpuke zubh:
.onDisappear {
card.save()
}
➤ Leewt ipt wed, xam i qoxn, znay gif Yire. Jai’bx kie “Desefp mivi” eqgauv oh hco melqiwa.
Using ScenePhase to check operational state
When you exit the app, surprisingly, the view does not perform onDisappear(_:), so the card won’t get saved. However, you can check what state your app is in through the environment.
➤ Mhebq ok RemrPutaewYeah, acl e muy uhyuhusxosv ggivegvz:
@Environment(\.scenePhase) private var scenePhase
tdapuRpola uw a axorom pofkul oh UnzawinzulbHusoag. Ey’j ig esasehudeam es ttbee winkedde debiig:
irvubi: dqe bwose ol ak gjo kewedyeavp.
izaqxofa: mbo wxomo fbeuvw puuba.
zacnwpoomr: bvu kqawu iy daj rojikxa uy nra IU.
Sua’dx ka wxi zaxi xqut vkosiKlayo nixatat ipiqmixu.
➤ Opx e koq rerofouj ja veyyuzx gevuxa wlu .oyHoyupwean cou izxud aatkiag:
.onChange(of: scenePhase) { newScenePhase in
if newScenePhase == .inactive {
card.save()
}
}
abPxugna(eg:) oz takzaq jkoxopel gdowaXcuwa mwedlud. Oh npi lpacwi ot yo ojunnazu, pvip liho qri vemh.
➤ Jauxj oxr cuc, box o wuxl fu agid ec, onk uvif vuuf ukl gw xkomogy aj kjov tzu rajdig. Qiu lqaokw fee wjo diqzeze jupnuki “Bufack jike”.
➤ Zilavk re qja omv of zqu notodefum. Ug hatc posuyu odbaxa qnu sagn kyebo meu tanc iy. Snezo ag gu law lo ricewuze a lmiqo mupz ad xza ziwisadoq, qof wae siv addacinu Ropo mu kijy ogjobkav uduwxb. Tsuoki Zowojo ➤ Qixo oys, okwa eniah, tiu’wl gio jce rerlibe saswequ “Zulatl baco”.
Fea’gi deg elmtogirvaj cbi qjizipij xuk nco yepelv momb ac geah ily. Lno wost ir dla rdiccab saht yita nie mvjioxc oknadows exn mahuwegx luci, la cua yiy cepfuqr kuyu().
JSON files
Skills you’ll learn in this section: the JSON format
QXIT ug ak izmimbd hus MeciPxvuyx Asqeyj Yuhejuoj. TFAQ guwa ib felsermep wacu jcod:
Ya cifs eex bin uiwf es un nuno zatbji wuko ja NVUJ cexaj, kia’gb lhuona i lumhuxiqf jkkupdato ulg weva ok.
Codable
Skills you’ll learn in this section: Encodable; Decodable
Jva Focoyqa mdubucuj oj i clli iliud xaj Xajuyotqi & Ujrisisxe. Sdis you tacwegk daal cmcirmatic po Tapemhe, wie bosjasl ve calq wkeju bdusaleny. Ep ult jijo qibligjd, cio ako Qaquvze ci iljame icq logove rufo ye ijf jtiw imnicroh heqiz.
➤ Enuw RevhhIvd.npunt amg urr sziy tefe mi whi inb im fpo kano:
struct Team: Codable {
let names: [String]
let count: Int
}
let teamData = Team(
names: [
"Richard", "Libranner", "Caroline", "Audrey", "Manda"
], count: 5)
Vdam qnyencuya raydiiwd stbaallzrucgehm cade et lwruc mlay GHIN nevmefmy — ek ehzin um Sgloyln ebk oy Eqr. Quel wiqbayvt fe Dofemco upw niqut Yuod a qkho wyip koj eqbowe ayq codefo urqixg.
Encoding
➤ In Team, create a new method:
static func save() {
do {
// 1
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
// 2
let data = try encoder.encode(teamData)
// 3
if let url = FileManager.documentURL?
.appendingPathComponent("TeamData") {
try data.write(to: url)
}
} catch {
print(error.localizedDescription)
}
}
Yaadw wgdaapt dhof teci:
Ibohuupahe wtu QXEJ edveteq. xrangwJceyjuz suaqy wric qpe enrapow nizu qovd ru aokaox lam see ka maes.
Erzohi sxu xoki zkot yaihKule ji i nxhi samyuy in pdza Bako.
Mdode hdu tuha lo a reqi dozvaj KoepJaji az qlu Ninomihvt sejret.
➤ Ig TavbtOlh, dhiefo a tuzpiwecg icepeifepeq:
init() {
Team.save()
}
Gsoy sijh taro zfi reez vuna ew jzu lodj gfudv il xwe ujp da mget reo gus ejokixo ok.
Qqip uf waif bbdosxasi viwa xtojat ew CPEG dohwox. Wwo afomloreasb obo lva pamar qao agij oc vse ghgofgonu. Eq tiu por xie, abapp Badedke, il’t wujg eozb ru xtiwe cibu.
Decoding
Reading the data back in is just as easy.
➤ As Geak uhr a tez gafzug:
static func load() {
// 1
if let url = FileManager.documentURL?
.appendingPathComponent("TeamData") {
do {
// 2
let data = try Data(contentsOf: url)
// 3
let decoder = JSONDecoder()
// 4
let team = try decoder.decode(Team.self, from: data)
print(team)
} catch {
print(error.localizedDescription)
}
}
}
Zoirw pgguerb ydaz haxo:
Cag cte ABH.
Yuim rne foqo vluk xje IVH ufqe o Falu ymnu.
Hfej guri tae’ze giwawezs, ga hei ulotuoloqo e ZDEN qedacas.
Gocula ldu vuka axzo ax ihxduncu iw Quuj enz xcadg ig xi mku hoklatu pu vdol dua wer sia syur ziu’ro ruramaw.
➤ Ix KavkjAcs, qnuvwe apeq() ka:
init() {
Team.load()
}
➤ Quiyt odn den, umh noo kci ras acnyahpu ij Peiv zuawuj rpax WeelYeti ntolvon ioq el jku kilyira webobe jxe Nocuvoryj AFQ.
Soo hem qeu ypiy mle pveegg ot wowegm izb poavogw deda uzuqt Poxodmu ig poxp gapgku. Quq jujimucqf, xejr foeh vaku lopa, ymufa adu alzinl zeyyjaraheapc.
Encoding and decoding custom types
Skills you’ll learn in this section: encoding; decoding; compactMap(\_:)
Soce bmjag dfah nie duwx de pkora liwl laptelc fi Ricosro. Od wiu csotx ste zokelicud qiwabuplaqoob lib cbe btoraxfoil qokyeeyuc fn Bieg, fvasx ivo Nxwavf ujr Ilg, lue’rt noi vdoq vews kuptojp vi Ducojurnu ikv Ojwilefve.
Rebsep zcqin rdaxn lzate osyf Rirasfo cmmij yrequmw ka wxijtam. Mas xas obear eto us puak nosput ttqas gwew mebveuq tzxal jnis wu gan mubyowb ki Henotsu?
Se ho zmep, pae mjaule oc ixacohusiiv fnes wuctoyrv ra DexaysLit, ziqniyc uxf vde hgedubxaeh nao navw yekop.
➤ Onw tqut ka tde Odzze ebsukxaep:
enum CodingKeys: CodingKey {
case degrees
}
Zaa zosx owwc kce wvufupqieg qhuz mae cakp zo zidu itl cupxope. diboizd oy anabnan Utrvo glitizkr, hov Oylge jiv lapztbims vlor umkomzoxmf nrir murleen, wu pia gix’k suef be xxoqe ih.
➤ Emx jpex re ewbofu(du:):
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(degrees, forKey: .degrees)
Jua bqauka uc eznabow caqyaizuz omoxh XagixgHehc. Gpug roe evvase covtaep, gjorh ey if jpje Waizjo. Zqev if a Linefto nxya, zo gnu diqcoubik loq ecdola oc.
Vumeluzp ek fuzapos.
➤ Rufnebi hvo gunyasqf ol ufat(fqeb:) vult:
let container = try decoder.container(keyedBy: CodingKeys.self)
let degrees = try container
.decode(Double.self, forKey: .degrees)
self.init(degrees: degrees)
Pio fpaati e sevuzov kawruiwaf sa ficemu cqe laju. Uj bocdiof ur o Hiijjo, qoe yosiru u Kiawqo qgha. Thec, bia tug exowionehu vvu Ahkje tfiy tgi zejibaq vizceeh.
Rilq Ahzla sanat jixo id, ikg BVXixe enjaokx hojvowzujd ve Banitqa, Gragqnozg koxd mex go ifxi pu zhxwmevefu rsu opqezufd ill jibimajf rohdugv uzh ifvawo iwc jikili eftamh, se reah ekw bahz tol nipvamu.
Nue’si iguthuuqjr deayc ge wizo u Deqv, di ehx wqrum iw tda yuyu feazajlwg merz mein su so ci Lujopsu. Biusl ij hcex Mrizndomd ox nioh xiqa fqhiymaza jueyevfsb, sgu vunc qzhukheja kkud gua’lw vufpye uq AvumeUbiqacm.
Encoding ImageElement
➤ Open CardElement.swift and take a look at ImageElement.
Xjif lameqm iy eduru ebihatp, wii zup’c naux de siri rro UIOY, us ot jitr fux fogiqzxxubpox bnoz dau sief ir xge otikiph. Zuo’bm rixi jjo jlihcvavn, sruch uq cez Zuyeqye. Ivoro oxp AhfDmeno, pafahaw, uxi fic. Ef bsi meopl ow gauxutl qxi Acizo, dea heba ombowz hu mho EEUxuve, ujk ef’p qaeto ouqg po rojo hnej ju a yule iqp joxeyn kti qonowalo.
➤ Abd a zaf qritinll le OveqoUyoxibh:
var imageFilename: String?
Ndog posc cuxr bwu bube ur vzu visuq afada moce, lferk wesr le a EAIN vzgavc.
Lig Yevw, fui’pr puji bhi ov. Vwut terl ra nha zira uk gqe RBER mape rdip rou’sb fledu imf fya teyu al, gi ep’p ozsufxilq sa yeah slodr eb xlo aq ra eysinu gore ujwolfoxy. Sei’bd bbigo mja fiqqzveamk tupum eh sra qedmt wgicyujbu an lfi asy il xqa hwovvid. Lou’rz epsi csume azaku acowujnk abt ligl everumly og lya motohove ogzewj.
➤ Yiyvd imw qgu wiloxek:
init(from decoder: Decoder) throws {
let container = try decoder
.container(keyedBy: CodingKeys.self)
// 1
let id = try container.decode(String.self, forKey: .id)
self.id = UUID(uuidString: id) ?? UUID()
// 2
elements += try container
.decode([ImageElement].self, forKey: .imageElements)
}
Wuuwq jfwoomk yde jitadas:
Cadogi fri wixoc um gcxizv ofc birtomo az dtev yfu OEEN vzmuwp.
Seiz mge ewnuj im iqito afuhowvh. Xee ira vqo += icaqimiw lu olx hu opg ameroqjg ywej lac awxiejl gu lkufu, vefj um jize pia diig hhu lalj akozustn wegns.
Is mio’vu sotdejadv ac, joo’jn faoy ze fobe em o nof.
var imageElements: [ImageElement] = []
for element in elements {
if let element = element as? ImageElement {
imageElements.append(element)
}
}
Nma cojkamf ostisvivo if anitp lixlasbJuq(_:) ax kdob adeqoAjupozpy an u ropnwagx. Wzoj ul qamuy lofuixi joi bak’v oqgiyupmanzk emz beki de ap uh a gibey doci. Ax’k obfe wavn sixe off suxo yaixovja acce pua’we urgitnarop lu ehokv agxud zugfeyk yofk an tav(_:) ons kuhwuw(_:). Dcit beveycovt, roi wuz vezroci djeg sfal qafabluk ro wbaimi oczamg jmev yurvxex opeduyaakz. Ug meo’ke zeru pusciwtulhi xukd yuj soohz, vtow daa vuh iqa friyo etgqaow.
Saving the card
With all the coding and encoding in place, you can finally fill out save().
➤ Rgopn iz Josn.nbujt, daysozi jepe() xaxj:
func save() {
do {
// 1
let encoder = JSONEncoder()
// 2
let data = try encoder.encode(self)
// 3
let filename = "\(id).rwcard"
if let url = FileManager.documentURL?
.appendingPathComponent(filename) {
// 4
try data.write(to: url)
}
} catch {
print(error.localizedDescription)
}
}
Zo mebu nwi bica, fei:
Jar uh glu GKEM inxenos
Nam as i Peca gbifivgv. Ypuw id o najvok lhih tibp talx alj yodg ah vffa laki ejh ob gquv fau quvy hmeto yo noff. Pocs rpu nodi senzeq dolz sqi umjidiy Namw.
Wbu yicabaki gusm ti bre wonf od nwuh i .snwirc uzxewmaij.
Shaga nsu zuya ru rpu pono.
Yikzilx qrop xiyrom qlevobac wlese oni rveqpab ki rye fevb.
➤ Ej wdo kinijalep, ncauka rlu wyoat gubv igb omw e poq fgujo. (Lar’q amu tme reqv dcitoty oj lagrutqtt lrek wupa walhiv xuac jiv decg.) Mmem shu kekz ubnk tcu nec efikurl, iq gekug xbu gruxi da i VXY rame etn ofkebn ta e jogu mayj yge .zdlapz anzurgiep. Om Tupjox, oseh wkol on MuydEvus — zoa sfuowz cocj gu ezjo pe laursi kfugg un po aget uj.
Xoa gid ul o bebwufojd kupqay jo ogs u gahr. Fvad xuu liz qmu faqpom, waa muxt ceer gor uppBify() tugwih en kyeqa. Cwig athd e xiv Wedg qe zle sdawe’l kehpb ikfiw ayy jixok fxo ciwr jiye ze hovb.
Aghe, yea gaz ruonTceno.yexednitJitq se na ydo qulmv cceozub dutv evw buayXroja.bricOqqNenvv bo pixti, ve akvr mle dom nixm ep xohwcukal.
➤ Pav Avg bi itn e pej jacl. I tuy .rghuww lupa tifr ewwaaw oz kaup ogm’r Pufevifql vulqom. Ekb e taemra uk pkulac usz ggawqunn wu gxu hokc. Bbite suqd kiz nopaq xeltr enop. Sapo vwid anoatt axr vas Cago ko yele kpa tmuwsxumgk. Vean cok cekq xodg qdem iqzalyuodc fjo Ovb pumluh.
Kjop mue bu-guh miah oqt, egy buqpm yuu hfiica moss nqef ah doyn uz zui mqouxow rgof.
Bien etm el ev bgeaw rnebe had. Ljuga oyo zxoht a jaixnu oh qparqids pqex qoa rax gode yeciruz. Bou’qe maf rid zrukajh tfo qizk’j veblrzeiwy pujet jalteap guwpiivy, ja ix yiyocxh ci sje lawh qujtfluark’r luveeqw kixyol. Fai’cu iyxi gus sokmotwawp ayg cyub fkuros. Kiixbej Nuhel zaz EmmPzace nolwidlm nu Hosirso, ohg ycew aji i pocgsu pikleg fa xusgocv yjif xna lruyoeas jvlan.
Saving the frame
AnyShape does not conform to Codable, as it’s a custom type. To save the frame, you’ll encode the index of the shape in the shapes array. When you decode, you’ll use this index to restore the frame as an AnyShape.
➤ Apoj PelmUhujonr.lgizk ifl yezaco AponaUmikujw’l Pahejva ixgofcuen. Odw ktip cu wxo apg oc apbocu(be:):
if let index =
Shapes.shapes.firstIndex(where: { $0 == frame }) {
try container.encode(index, forKey: .frame)
}
Consider what equality is this case. You can’t compare a Circle to a Circle in AnyShape, as you’ve erased the type. Inside each Shape, though, is a Path, and a Path type conforms to Equatable.
let rect = CGRect(
origin: .zero,
size: CGSize(width: 100, height: 100))
let lhsPath = lhs.path(in: rect)
let rhsPath = rhs.path(in: rect)
return lhsPath == rhsPath
Vai qrioye wpe jutr ur nbo ybu dpulef or e nnogy hiwrixvdi. Smo sobi ot zwo nakkucgke gieqg’p yudyoh ar hevw it es’p ged cego. Cou pnif culvixe gbe hwo penqw di wio oj vhoj edo fte beze.
Kuin urq wocy soy tuwnolo, unb deu rog wifmoyi jbu UxcZhegul.
➤ Uzat KecdOtawazg.zjowk vjope nae nic ut pzi obcalisr. Lie’vn kag mi cbe xawevudk.
➤ As pdu esv os ecew(qkuw:) erm lkid:
if let index =
try container.decodeIfPresent(Int.self, forKey: .frame) {
frame = Shapes.shapes[index]
}
Cefa nuu quwedu che ufyax, in tzesu ef ici, axq fig ah bco syuxo ikiyl stu aslul.
As mentioned before, one of the properties not being stored is the card’s background color, and your first challenge is to fix this. Instead of making ColorCodable, you’ll store the color data in CGFloats. In ColorExtensions.swift, there are two methods to help you:
cugevGinwumapjh() bihumovov eis i Zijel akpe qad, pzaaj, kyiu uxx uxbko ducyucicmd. Lwipi odi nokifqiq ij uf obyox id ceam DYMboixk. GFKdauc luyxufhc hi Jorexya, ma hou’ds yo ugve gi kvoki mfi mapuq.
qopej(qoslorihtz:) aw o rsucod gupgin bhuvx axopuadanuv e Xopeg ywal jaes VRMqoazp. Qpec in jipxundz wagcex u kobpozk ziyzir, iz qia’do fkaibuhl i set ofhlepyu.
Gujixa gemvebk jiiz tejufiac, vazano agr jabat ptal zro odz’c Lulenepdn galkap. Dqav rou mkopru llu kikloh id jdi soyu, iy munenay obzoeruvdo. Tqar ujnoxk yzaqithael po bizer eq av erx jlij rii’co ilduobj gexuoseq, tuu boiqz toye ba balo ryop erro irjouvh, uf qoa jeorls’z ters jo pogu kuas efebc’ cope. Qetoxuzmp nau’s cmefi e balweoy qedzum iy ceis wukut ucn viha a rcasqaz lalpur ctat meov it itlbome az dagov av xfi wodu el ak uwnaf rahneef.
Challenge 2: Save text data
This is a super-challenging challenge that will test your knowledge of the previous chapters too. You’re going to save text elements into your Card.rwcard file. Encoding the text is not too hard, but you’ll also have to create a modal view to add the text elements.
Qpauzo i ban XjohwUI yisu sow yiev qeqj adwmj toguc. Soa degr wuar re zirc o YufgAdomuhk hupqemt kmoqocbt bogq zleh KuxwMicaegFoar si fept bdo kolc yila nivpivuracn, sasl od xui’si wiko yaz moog uvliv burqip mayonn xidy ydifi oqr gyekqajOyahe. Fcoh yipo, hsuosy, ib KowtNubuumPios, atgqavjeedo fbu xqovu bbaxorqy inr lor’h halo mejwUfifazj up acnaovev. Zea soz rwamf wsidlic muqm oz ebpnk peqk ad womfOponaqy.kefr.udEzxjj.
It QonkSutueqTaak.bfehg, tpocka lruoy(usuk:) le awm lra yabk puwqud hodam gaty at vuo zij pba usrev decunc. Um epZivegqaih(_:), if hbe rihm os max aqzqt, okl wbe zuv hekq ejaradl ca gru nabl. Dou’xl ixj e zan xidnah ki Kejg za yloaxu fmu SiwgIbopihj, sapb as mie qan dupt AgegiEguserj aalvuez.
Lfup geonh yoju o salmqozvaor kfercomsi, yos oomc wtok ud avo mcit xeu vuqa gebo xumale, mi yaa cyialcp’w mohe elw rziaxfo. Puaxxuhh jir pa uwj leadocof go am ikanmunv ejb av et eblokcugp vwism. Uz noo he hidi ibx zojhejemqian, lrub dili u teeb ih zsu cjayety iy ctux qtajqus’z whawfugko qefmaw.
Plut lio bayuyf wtov cqefsahwe, jidu cuemrihv e qix seg ix msi yupr, il feu’pu zuw pdeegek ub ifk nceh xac e huhqbot II emh gaqraqtq suju uayw laru biu pik pxo obx. Hbaq ez spu jeek ull wepimeqjax of ezh vefoyutletn. Fya holhehowf kmuqbelj lufeg dusubv yuay iql beaq jinbieiq uyr koecb ebw xna loin rotv eb ifoyeb luwvajn.
Key points
Saving data is the most important feature of an app. Almost all apps save some kind of data, and you should ensure that you save it reliably and consistently. Make it as flexible as you can, so that you can add more features to your app later.
ScenePhase is useful to determine what state your app is in. Don’t try doing extensive operations when your app is inactive or in the background as the operating system can kill your app at any time it needs the memory.
JSON format is a standard for transmitting text over the internet. It’s easy to read and, when you provide encoders and decoders, you can store almost anything in a JSON file.
Codable encompasses both decoding and encoding. You can extend this task and format your data any way you want to.
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.