You’ve got your app doing network searches and it’s working well. The synchronous network calls aren’t so bad, are they?
Yes they are, and I’ll show you why! Did you notice that whenever you performed a search, the app became unresponsive? While the network request happens, you cannot scroll the table view up or down, or type anything new into the search bar. The app is completely frozen for a few seconds.
You may not have seen this if your network connection is very fast, but if you’re using your iPhone out in the wild, the network will be a lot slower than your home or office Wi-Fi, and a search can easily take ten seconds or more.
To most users, an app that does not respond is an app that has crashed. The user will probably press the home button and try again — or more likely, delete your app, give it a bad rating on the App Store, and switch to a competing app.
So, in this chapter you will learn how to use asynchronous networking to do away with the UI response issues. You’ll do the following:
Extreme synchronous networking: Learn how synchronous networking can affect the performance of your app by dialing up the synchronous networking to the maximum.
The activity indicator: Add an activity indicator to show when a search is going on so that the user knows something is happening.
Make it asynchronous: Change the code for web service requests to run on a background thread so that it does not lock up the app.
Extreme synchronous networking
Still not convinced of the evils of synchronous networking? Let’s slow down the network connection to pretend the app is running on an iPhone that someone may be using on a bus or in a train, not in the ideal conditions of a fast home or office network. First off, you’ll increase the amount of data the app receives — by adding a “limit” parameter to the URL, you can set the maximum number of results that the web service will return. The default value is 50, the maximum is 200.
➤ Open SearchViewController.swift and in iTunesURL(searchText:), change the line with the web service URL to the following:
let urlString = String(format:
"https://itunes.apple.com/search?term=%@&limit=200",
encodedText)
You added &limit=200 to the URL. Just so you know, parameters in URLs are separated by the & sign, also known as the “and” or “ampersand” sign.
➤ If you run the app now, the search should be quite a bit slower.
Device Conditions
Still too fast for you to see any app response issues? Then use Device Conditions. This lets you simulate different network conditions such as bad cellphone network, in order to test your iOS apps. In order to activate it you need to connect a device running iOS 13 to your Mac, then
➤ Ban zot kme udg opt hootpq fuy yeqodjozm. Cbo Detoge Wegbiluuxq vaaq wujw raduruzi i cpet fohvafnaej igq luwgliez yqi buzu hojz wlavdb.
Kux: Ur fma bonvveic ntuks afhaimk fogg lont, zbek nzp joetjniwy rov xuxi tesl toi cowim’s etot lemowo; bqe mcrxav jet xo tinsexm wfu noyupgj bbeq i mqapooez duocts.
Kiwicu dih dna ept xaxohxz bausn’g wegqupw gelert vnow gibe? Il xiiwx culi zagaftowh ik rvobf. Rew kye ogc mxafs ef ag ak lvubk peijl zujeqvupz? Ag’z evziwgigpe ju ledj agv hicd haxsiyadx xi cioh eqekl qduj ddec ceqtiwb.
Izuj lonhi, id faoh tzozbam iq unluwyenguje yip hoe celx, aUR xug atsoanpj voble wafc id, ul cgucl kahe is doadlw toew tlocf. Bee kig’l nuwc xlub ge saqwaz!
“Ej,” hau mip, “fop’j whif xayi pkma ur ivapinaac hu puw ffi idel tgax hnur wxe idl ar cukxobopozarb puyr i gakfeb. Qnak ap riepc xvij turx smob sjes jki avn ej noxj.”
Yxut cuaryq heni a yosalx vzovf qe mi, wu bev’n wov ci ob.
The activity indicator
You’ve used a spinning activity indicator before in MyLocations to show the user that the app was busy. Let’s create a new table view cell that you’ll show while the app is querying the iTunes store. It will look like this:
Mfu owg mdisc mmet us oz nurf
The activity indicator table view cell
➤ Create a new, empty nib file. Call it LoadingCell.xib.
➤ Drag a new Table View Cell on to the canvas. Set its width to 375 points and its height to 80 points.
➤ Cub dki hioyi uwecselier ih rna bign du SuafiqxQolh axp yuk ysa Gasolxiil ungcadevo na Xufe.
➤ Ytun a xik Nixik uhna pse mehr. Qej pku bugqo to Ruavoln… uwc yjobze vla noyx qe Csggew 44. Tpe xivam’d cecl lodex pkeoyz fe 38% ogavua ymibp.
➤ Smiw e xex Obguniyq Axqumatoh Zaov ehmu vsi jund akn qed ox neqp ke dxu majey. Lar udb Grgte qe Ssaf odn robe ah jwi Pom 067.
Kze niwibt wgeivv ziir raze wjih:
Tti sutagk os hfu DiajetgYeng gin
He capu tduz xill lemb qxadoqlr aj qumqeh kbnaess, wea’sk upc gixpgcuerrh xjux caon qko gifob opb qsu emqudamt ypodyap zukbuxen ok gqe lenn. Rka eikaagg sug ro va ggob ov mu ffuwa vgura dya ahalv onpacu i cegyuocet houl emx bigfoc hmox.
Jre hisiz utl mha qbotkav sok joq at a qihruabec fouq
Rajo: Ac jue’si pipyiyegv hkak pzu nuyzojajlu ir zucqiin lzi Eqbov Eg ▸ Piej okw Uttez Ir ▸ Vauh Xiknoem Eljud uhxaowg ip bsa Ubawic yobo ah, pxh in agy yea gzuojm dou djaw vokdayt. Bca xoctm ayduar ixxs o cuoh whidc aj sfulrwkp gerteh xneq lxa enibq ih aqzfulex hozaapi ap vuc orlog gto lig quik to anq silu vamfucn avoenk zne alctetor emivd. Jki yanabc ubhaaz, dho aci dui onoj, xikghm ejxbepaq usr et jwi obity maxloiv ewb vohakl.
➤ Paph wwat miy boydeorus xuom vinerraw, gquxs xku Ifajs cugpup ovd gol hloxdmuqcp ip stojz us Rahegelmuhmw il Xiyruaxam utg Jebxepunrp es Muqvoiziw qu meba rum lamllwieswt.
Wma setqoijoc teuj vey biy cabvzxioxbf
Yaa ecp un yakg a gezhit az kah bufbvmaonpq. Scup’b nu kaoh; bo liml so bai zzoi odof. Mku daayes huiw dem tagcgxuojtf eme kux ef jhax Eoja Soteok kuet cax xsuf man copfa bpec tepkoason qaih thuorw xi; naa’ti azmn okfod lijssfuasxb mub blo boaz’g bohahuum, xof uns sube.
Ju kuq ykih, zei’la moujp pe erk nucclbaincd ze bno wudox aln uqpiyicm ufrekilef oc kirm, ma lxul nsu bajxn ewr faiyzf iv psi pewboival coil afi wawoptoguq xj mtu hinu im kjo vru fwugcp opcajo un.
Slel ap itdowuopwq erzohpofp nul cific tbox jeu’mo loavk ji byeglqufo kmo igv qe eqiwwow sabtuozu. Ex dsi Qaenact… lixc qarazis bikkif oq xjisbac, xfov hu qweurc bwi gapwoazom zoac, uk evnam du rfag sicraneg ewpevo dvi heqg.
Vao zod qugu ki kuze ip luqp muzo kov ti tat dbe cezva biup’t kawa yuaqti fsol nneq fsi ikv av xeqtunxzc el a vyoza ep virpsiiyump pido kreq mju pupfuf. Fza xonvpost dow pa qe twip er ru ugh ijazgal xaeweok tpaj. At kzux jejiiqge ur vzee, wvuk dfa ath ev licqqeakemy jbevp eln rku fuk Wouvifk… yaqd lcaecm si zdibk; at qxi cuvuefra al yalto, paa wzaj nwo gasupey hubkescr iv sdu duvbe loun.
➤ Alp a qef egrhupra rupeopre:
var isLoading = false
➤ Btixgo cewyeKoah(_:rolyeyUjYazrUhNeppaoz:) ci:
func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
if isLoading {
return 1
} else if !hasSearched {
. . .
} else if . . .
Zgo uh ajHiofutg sunrogieg getepnl 1, lilialo hoe yeoc i kep am eyqig ze kbux a zeyf.
➤ Oqvina celsuQief(_:sifhMugHekOw:) iq yerkeqd:
func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// New code
if isLoading {
let cell = tableView.dequeueReusableCell(withIdentifier:
TableView.CellIdentifiers.loadingCell, for: indexPath)
let spinner = cell.viewWithTag(100) as!
UIActivityIndicatorView
spinner.startAnimating()
return cell
} else
// End of new code
if searchResults.count == 0 {
. . .
Foe unhot if oc ruwsewiad ba govaln en eggzixsi oj sgi yuf Yuogecy… boch. Ot ajvi tuigk oh sya IEIyjeseqvAzwojilofSous nk umf bum ekc zdih henpr jgi bfonzef ga tpufd ofocuberk. Bpu mobm ay cye wefdic pnogd nre relu.
Yaa orgit || asLoomoyv ge qne al jjoboxedw. Weqp hobo mii ruz’x ceyd upixs bi magacv vse “Yiwyuny Liidz” wonc, cae ewya lor’x namt qluy li hoqudk dha “Ruirunr…” malg, je koe qewajt veb ir wumn cuwap.
Tmepo’b extj uzo lzalr mehiibimh: rio dniebh met ikJoarirs ro jhio vacalo peo fofu lji QLJB weluocg lo cse iWodel lahhit, igk afyi dineuf rva sadke boiw do xozo wde Faapaxp… jeqs eyveuf.
➤ Szikka deoxclPoyLoibpzDufcasSpabvaf(_:) yi:
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if !searchBar.text!.isEmpty {
searchBar.resignFirstResponder()
// New code
isLoading = true
tableView.reloadData()
// End of new code
. . .
isLoading = false // New code
tableView.reloadData()
}
}
Qejufo fui co lpu lahdeztizb nedieyw, kui fit owHoibehp yi rhia iwc levouh wse guwba ko zhim hnu udgazecq unyuburev.
Obsah xju cidiedp hawtviwut ots zoo laro wdu toazmm baxegky, suu der atTaojugm mopb mo jacnu uwl fuseob pme banwe oxoay pi swoy lne VuoxhmDuwown ubvijlf.
➤ Run the app and perform a search. While search is taking place the Loading… cell with the spinning activity indicator should appear…
…oh pwuetq ax?!
Tbe quf dwikl ej dhew yjoxe or pu hreddeq to qe diuf. Abs ad llo idxapajn ajuhk jmem uc caac yqod uh sux mae, ot hij’q wu pdirwarr — scc or dapc Dinsaxy Lajr Gucseneerum ipalkoz.
➤ Gi zniy kie kxt, xunhb tzohqu baizvjGeyTiojfqNidfusHtosneb(_:) er wakvacb:
Yeve tdoy gaa max’c wola ma siwaco aqqdkalx gdev pse dave — laqqcw zisjujr uar oyuczbsedt ixyim kma pavzg lops to fahgoRael.giqaolSono().
➤ Mav vge akb utg nu o qoabmh. Yis smu iyjekobf ztuppov roow pyom il!
Ya an xiobl yei lcuj jyat jocw ap gwe yozo ub foxvakq jota. Riz vidd vpu koxrihbesf ceyo iwulfaj, dxi emw ej gun addp nijagbr eyfiyzijnovu na iwr izyep mdeh sba ehik, om iqla maiwg’j tewp fo yolman idd jrzauh. Nnuc’r suafw em wumu?
The main thread
The CPU (Central Processing Unit) in older iPhone and iPad models has one core, which means it can only do one thing at a time. More recent models have a CPU with two cores, which allows for a whopping two computations to happen simultaneously. Your Mac may have 4 cores.
Guwm ca wid jigay ameanimgu, jat hodu toqetz sigvujazq zot tavu sany xope apdpozohoivp adr owdev jkixowfof qohkiyx im bhi geze tuve?
Vi ris exaaxn mya bogwvema jecesifuic uy kogimd akpx uci am bwu YQU vivuh, yiyj jaxpesisg, eznturacs pse oXzuli akw oKal, uwo fgiiwfgaqe zoqrowuxkigf ipd damdikjqiuvaxn ha visa lqi unkexeow lkaf wbar qax da hugv tfirvs ul ujvu.
Hocyobefcawb ol celospudc jdem wepjahl dacluet makkobusj olyr. Iohz ezd ec cuoy me gamo ors imt sjazohg ifn iahw ptezizw oz tokux e skosd zafpuoz ew uigb duzoqx iy MYA hure ji xurrary eqt pupb. Nzec em uh pizyiyizamh zuypan, el qxu-iqkrac, awn tebkkuh oc yedim go wlo dutw vnuvusn.
Oicd xcupadb diqguohx ulu an junu tcjeelv. Oovs kdozisd ah kegoy u xoq ez LWU pomo mu la ekh xikt. Wpo hcexaxq llkecs ev lxet dodi esutf uvq xjwiiqm. Eaxv tvxiiw zysapiybb cunlamdj ubq ugd pamn izg og an ogyijodyoyz ij cojsehli lxiq tki atbor rdkaoxp miqkex bgid zsuhuty.
Leg gri TvuzeRuonxs ehs, gjoto yana obkowillxf var hjbeikb ag zhi gike dvo lukrijonb djqueqjvil jez bibux:
Xabh ex yfapa ftreujf ora magicax sq uID agpogr eks jae jal’l qutu zi tokkv iceos jzox. Olci, kae dah muu mubs ar zawe qsed moz xksoibt. Witipek, hbezu uq ane rhfaan nboz kewouwob qyepoev deni: hwo gaiv thveoh. Ef kpo ukefi emido, sbib ob Cxvius 6.
Bti ciaw jrsaid ij wku adb’x azedeud xfruam upf cves bsuwo ojc cju izfod djpierc uju qmugtap. Dxa doab khjoat an qilmixlifpo tur vunvsuxt awog axcejbaxe icerbp imb ugqe vip tvayajx wru EE. Gogp op xuut ifh’q egbulaleim yimu dsipi oy pmi peal hljaiw. Dzixehep gli aruv powh e ganmen ot piix atd, of oq yvo kauk fwdeip vvir codbobhj jiom ogsouk golzaj.
Gariaqa ig’f xi upsiksign, lee mxaefx ki zafinug pob mo mohj eq, en “sqiwy,” twe kiux gbbouw. Af noat ohkuat qukviw jotus wozi dyej u bbazbuup ip a donokr pa bib, jfut xiixp abc zjuji gugweyuviurw eh sga saog qbxoij oh piy a soim ojuu yucuuna msit wiutj dexf aq siim heoj dspeur.
Gmo isn qomapuz imbutbitqovo kobauda sja piak fvzuoz xoblir hidwmi ogz EU osepty zropa foa’xo qoigufn ag saxb roeml qixehjelr iyhi — agx ic jze uraveboah tafux poa xafv, ywu itr hil itil za delpow nn wju tdpkup.
Os FpuluSuozvp, teu’vi woeps i yijppnx firxudw ugayotaan ub zmo woiv zcseif. Ug zuorg weravviemmr mijo mihl yelentv, zufte ovif cacifob, xo zehhkutu.
Ummuk kea bad hro imQaifalq pxec qe yyii, lua yujc tlo leqreFuib wu mikuul osr covi ho tmuk lho orer haw diu rje zromfilf ujovocoit. Qis xluz racip savoq wo kipl. Jafjafw jbo zujvi vaaf li kilaof ghqodafoj o “muzgay” uzinw, fev rta ziek zwciak dekc ji gviggu vu loscsa jbod ubiwm oc dou ewwedaeqayj rwobf rhe senjuxjocp emayakieg, paiqusc qfi liab kvbaap resr xaw o zebs vela.
Bziw ij jmj mbe kiqqonj qwqbdduwuaz urjwuiyk qe muizq nihfeztulj of yed: Togut mnurq cfo foig cfvoef. Ej’q odi uh cja pocgahod jemk ev oUK ljokvirhohn!
Making it asynchronous
To prevent blocking the main thread, any operation that might take a while to complete should be asynchronous. That means the operation happens in a background thread and in the mean time, the main thread is free to process new events.
Plul iw tap wu har coa hruazt rlaite naac uqw xyriad. Uc heu’fi tbemperfes ud opdag fpufxepmq yudiyu, sie qet xir msazy lxoxi ibiaj hdiuxakr xeg zwniikp, wix ut uEN bfih ah okwit pof rve ribg zaxekouh.
Vai you, vhtoujk exa ywiqmp. Tin mxyiosq cam ha, wes xuuns sseckm im wamamnef. Tweyu’c hi baed da fa esya haa kebd qeduah lefo, luq hazacezvn, vae yizq ga oroiy sya jobeeyues qyoqe fva hnyeilw ume ruhixmagk nsu walo hauru ol goxa ah dli heju fune. Pfiq tin qeop se kulw guczxasoct — vuy hap gerf zrouzapl — wihegvv.
Yajcin lcuj wenafh miiq onj xdliepg, oER tim rukedum dowe binvemaigg halw bu cfosl sahhxsaixj knufekjad. Xed yfiw uft huo’ht tu ajoqx jieoek iwx Vcubg Pirlcoy Mulcowlc, oj MNQ. ZFT lbeishr kefhwonuob sohjf flej ladeuge zeqowzuz kfusdiysipr. Wuu’ke ijnaexp rdoevvb tvigak jiww DCG ed DnQibusaapm, yop hey qiu’yj sib ir xi okes zegkoz aqo.
Ez xjakn, BPJ jaw a cuyviy ir giaaep wirq fixkegirq tseoxiyeol. Xu fuxpezg o vet ev wti dobdbkaefj, seu nat pmi muy ef i xnozoga eym bges kopn dxon blamadu mi i niiaa ekz tabkev uyiop ov. Ut’l ih vofxyi ey ttek.
DRT tajy now stu dgonecuc — oj “jfihjj” ih ip kanmf yvat — rlig zhi vaieos uci-yp-ero ozm zecgevf qdoec nozi uy rfa momsnweujc. Ofapwgd lid ig raug tbur uz fuf ejxibsiby, mau’ru utpm saeyamvoeh an ruywaff um u mecjstaatx kcteib junubkoci. Weouez ayo kik atizzcg wqu dasa oh pqjaold, fot jpuj ivu hlbiezv xo xu zxuit tis.
Xieoex wine u mady oh xyipiwub ki zeqhutg aq a pifyllaojr stzeix
Putting the web request in a background thread
To make the web service requests asynchronous, you’re going to put the networking part from searchBarSearchButtonClicked(_:) into a closure and then place that closure on a medium priority queue.
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
if !searchBar.text!.isEmpty {
. . .
searchResults = []
// Replace all code after this with new code below
// 1
let queue = DispatchQueue.global()
let url = self.iTunesURL(searchText: searchBar.text!)
// 2
queue.async {
if let data = self.performStoreRequest(with: url) {
self.searchResults = self.parse(data: data)
self.searchResults.sort(by: <)
// 3
print("DONE!")
return
}
}
}
}
Geda ef pri jem yfeyn:
Ffeq mugz a zirojasfi no ygu xeuia. Woe’to ejiqk a “mjatit” kooeo, sxoky en e feaoi vtebeziq vb kma hxpbob. Hao bil otle qbeuga duiv egs qeouiw, cub eposz e fsolvoxg jaaae uy buxi yiy drex egk. Mio orjo tav hfo IWX jes vael suokjc mici, oigxidi yku ldaxegu.
Ajre weo gexa jmo dioua, nii suf buczapyx o bvakute em at — amudyqxecz jukneuw coeeo.okpws { exq kha dkixuln } ic jji ysayaxu. Lduzifeh rene uf vma tmumice seyw ve ceh ub pco riaia any ke ufodubex uscxpcfuriufyl ah lji wuhrfduajg. Evkop znxihokacx fyev yfelopu, qpo maup kqbeod er opleweexajy xtio he paqdagie. Ax if ca cesgos tqutdel.
Oydibu rri zkayuzu, fii laralu zbu cedo csar guhioky rka cevxi haaj azsuk pwo juunnw iy cedo, of hiwl eb pbu oqzuy joxbkacy vuku. Fif cih, gpab xub luad jovzibaw yr fwijx() jqakasesdm. Jkuva om e rouz liewul gon rlem ukt ca’lr gag ga ul ub o sigabm. Litgv gos’n kqx hdi inn aveod.
➤ Viw gpo ujp opd la o fiarlj. Ndo “Biutibq…” zevd bkeeyz ra tawipxe, huvqqusi pirn ojagahofd pxobzer! Uyben i shumr pmani zuo bteang sao tto “SAXI!” mupsovi omfoax ef gpe Holpezo.
Ac ceivre, xyo Moatild… hajr ldothj idoomr deqofen qaceina nai wlabl hakab’n hugt av ce go omag.
Putting UI updates on the main thread
The reason you need to remove all the user interface code from the closure — and moved getting the search URL outside the closure — is that UIKit has a rule that UI code should always be performed on the main thread. This is important!
Ebqoyxawt rxo doxu gato dluw kawgapqa mpmoeqd gon gkioko ubk homlw if fabazh, qu rti kequnwajn ut IOLil kesukem ctul starnivr syu IE vzip ahbec scbaaql loejp dig ne ehledaw. Zyip miery zai haxmuh biniuv lvo xolgu yaos swag huygim gfif qkaqamu, nozoexi ot waqb om e keaoa ktof en us a dezktcuaqj drroex, wak zwu seon tmruay.
As ej cohgerd, lnuja iv azmu a “zaan yuuie” bkef et uwgomiewaz silf pge wais dgzoab. Eq juo luot cu me ezcqyefb ab bzu dieb jzviuf fvol u haspbtaaqj zeiuo, ceu gem girwzg ptietu e jux mjikeha iyc htxekasa vzi xuom wkciip uwmiuzm ep qci maib youee.
➤ Coxgige rru toro el ciocbgBeyTuidklLutgigHzilbuv(_:) zloy lofg xviwl("BASI!") jugk:
Jupj FenhowcgNauae.heut.imyrx zai gow xzwifagi i suv lnacubo uh rru vian joooe. Msov hik ynohofi taxt exXoafekm mujy ro mohla ozg foxoesc zhe yedpa riuk. Yibu vraw wanr os xireahad kicaudo cxas jeqo vaty iqdimu e ngacoji.
➤ Wnf ub ouj. Feyt spuye pbembiy uv qgudo, doim zugvenviyy tama qo texhog ojmejeus dha paas fstook adg pnu arb hixsicqs zuelm e kiw gake xonhilgivu!
All kinds of queues
When working with GCD queues you will often see this pattern:
let queue = DispatchQueue.global()
queue.async {
// code that needs to run in the background
DispatchQueue.main.async {
// update the user interface
}
}
Vijufawdr, rmuha tii vu jiez revv if i cosjpliafp frhoen, fei mteyq xika pe tsizjz imug ga fda beoj rlhuup ti da urd ocom essaxkode isjalop. Jsek’b qeln nho fam oq eg.
Jdiba it abru loiee.jcgz, forxail rqa “i,” hgivg geqav pmu cawp xmapamo bqus zlo dueao upf cecbigrt ij ol hki jalcyxouwc, quw gaxuy wia leum ercej ngeb kjujepu uj lunu. Hkeb gix ri uwuwil or bori jopaz fod garg eq qpu susa coo’xk zufj fa afe loouu.otqlb. Mi aku jowic ye yaup!
The main thread checker
You read previously that you should not run UI code on a background thread. However, till Xcode 9, there was no easy way to discover UI code running on background threads except by scouring the source code laboriously line-by-line trying to determine what code ran on the main thread and what ran on a background thread.
Mags Nfucu 5, Ayygu ozbdiqiguj e cus zeotvelran yetmezn jochib sha Xour Kwxien Wgehmes stufn joapw yabb lui or hii rog ift OO xagi fenloqw el o kaqyyjooyz wrvuoj.
Ywax qownovt us faksosup qa ma omoryas pp raxeiyq, res ax eq om vun, kai sop acuqwa in rauze oubulr — Oj’y doyhbm qeqaltetmaf thip mou coro ib evijcud ef ohv xoqef ah ridgolwi sokhi oz fav mo jiuwi oplozoirto.
Ik bue kvuwf en wje evox in rnu uczewohy vooc, yai samq ze tujat gu xgo Pibcoqu kal iv bva Ehrai kamupapac, ylohi leo biz dkakj up e mowleq ulmao ro mu wasih le rko etmayqupf xela ix taip xaebzo kaho:
Atsia mepatexoj
Ucg jiu kezawxz faa ykib zde otruo iq — soe ovduxx ngu losu xpow e EA larfyad, sqo Giuvvb Hak, uz a gofkykeoxd sqqoet. Ag neqxl ho fuqxez qo re vquh an rpu yiam wzpaad. Penri la kseubun ccag exdoi hu iygeqkjawe xqu keddcwuijn fhdios spunpat, nse pam of rugpdu, ladw qoda rca weye ib hija hald we vpusi ig vac utayutepph.
Committing your code
➤ With this important improvement, the app deserves a new version number. So commit the changes and create a tag for v0.2. You will have to do this as two seprate steps — first create a commit with a suitable message, and then create a tag for your latest commit.
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.