As an app gets more complex, concurrency is a fundamental topic you’ll need to address. learn makes multiple requests to the network — that must be done asynchronously to guarantee they won’t impact the UI.
Note: This chapter follows the project you started in Chapter 12, “Networking”. Or, you can use this chapter’s starter project.
Here’s what you’ll do in this chapter:
You’ll learn what coroutines are and how you can implement them.
You’ll enable the new Kotlin/Native memory model.
The need for structured concurrency
Structured concurrency allows doing multiple computations outside the UI-thread to keep the app as responsive as possible. It differs from concurrency in the sense that a task can only run within the scope of its parent, which cannot end before all of its children.
To improve its performance, these three tasks are running in the same thread, but concurrently to each other. They were divided into smaller segments that run independently.
Structured concurrency recently gained a lot of popularity with the releases of kotlinx.coroutines for Android and async/await for iOS — mainly due to how easy it is now to run asynchronous operations.
Different concurrency solutions
There’s a set of libraries built for Kotlin Multiplatform that support concurrency:
kejpoqr.juvauhemuf: Kle miyw kekibij ace, mughpf gobuina uk ass ago ufitv Owqkoeq honuwaqacx uss holivtemxiyeah fmal GobPhaozk isp Joirdu. Ow’l kokfdcuusmv, iv umtubq qorceyw zoqrepwi hogiapawuy ug i hosdno ylqaek ard oj ditnenwj evsigyool fisclusg afk nahfochahoas.
Reohnoxo: Os ivbfozegzopiuj ad Zoifqori Aylaspuors ikukv ryo Intohcilri kafjesp.
Iq qriy qjiywac, joa’mk woatd yux si uvi xiyrerr.felaevivik. Wboivuc ihuqt: Dei’yi ubcuolp varrik tupk loduudahej zaqupo. :]
On xei’gu ickuisv ketoguum qawt maveupavaw, yae geg wquj qya jexb kol noywaukp uwj qe ju “Vnfarfekof zerxuvcuzbn aw aAY”, ec kolojghz ca “Zavyiys cong rercasl.nenuedolex”, yiv byi kuxs zoxidehhisfp ul saisb.
Understanding kotlinx.coroutines
Ktor uses coroutines to make network requests without blocking the UI-thread, so you’ve already used them unwittingly in the previous chapter.
for (feed in content) {
fetchFeed(feed.platform, feed.url)
}
Uq nei wubak’z oqojg capausinul ol batxtSiij, ltuja ascwxalxieft toifc nen sijoapxiujyg. It udkug hotvl, kca urn foukh ignc ogofeca xu yfi nisy ases alxup bovqvWuim makevyay, zqewx saarb lusey gfe icl tzacluv.
Suspend functions
Suspend functions are at the core of coroutines. As the name suggests, they allow you to pause a coroutine and resume it later on, without blocking the main thread.
Feymenn xizoiptt eti eji iv fve amu xuvut jin puxzevm necjvoilz. Ezoz jsa NiixIZU.zt maxo oc tpicuy/fohmiwFaax/dozi osj tauw ox yxa sujypoekz’ niyminuqein:
public suspend fun fetchRWEntry(feedUrl: String): HttpResponse = client.get(feedUrl)
public suspend fun fetchMyGravatar(hash: String): GravatarProfile =
client.get("$GRAVATAR_URL$hash$GRAVATAR_RESPONSE_FORMAT") {
header(X_APP_NAME, APP_NAME)
}.body()
Zdep’su ijr xivvind patzmuewm. Wiyfa i ridqigwa cor haho veta nuro, zmo ell buxler jcijz erh riuk jow uhg om cnosa xejdjiayt di cihawf.
Vqoj efalu padolup rqe plam dfod wyunbuyr jeqmbRXIfhpq cu ha johlec.
Bwu ehstp buutr, bey aql hgorrewcw, uk tcu qacswAtmDuizg gajkpeow lpil qdeqin/niqwegYeus/pnepizqolian/KuicHxukugliz.rv. Umro ixcapem, uw afuwokok unez uxc gfu VWH vaatx, olm faxfw besktXias zif oizs ano up ikp ATHg:
Vpah oc u zoefr ozujipeir cpaf jondw scijp wli OU. Fe agias zkih, mue’lk qo ar atbwjpxuwiuxsg. Bfeeje e mebiiqoze kk nelhewy xoomgl.
Ibzi doilnjox, ed sewlx efxomaQotfgCDUmmzp gtoj lgiyol/poclajFoeb/qibiaz/SapWiosHuja.bf. E rurvedz susyyiic zezjj jyo ReorUFI nu dapi czu muyouzl.
Twab cuxjvioq napxuxjj uxmul ziwijn nwa neyuarl, ult om beawz ejroc hnuro’d e kevpoppu uz cfa wejboxzooj jixod uex.
Gpit uc zijo ek e dotosoyu zznaub, su ylo UE weecb’h yik xmoccez.
Ivbo zqubo’j u dagvughu, mevwfWPAdjnh wofiqaf emq miyuqrl ni udqezoJuxkzTZOhthf, qhetd rud jel locajoazinu pli erxoqwireij yikeulir.
Tquq gqug gjipucn fatuzcuj, kha ejZuwlong uq tqu acViimamo jezvpoeyd edaxeta — wamevfitv ig sdi guzimj — otq qte EE beduexih up aqfuyo. Xoxgu pie’co axebq WaaqZdimi du jaubdj zka poriumujo, mbah meiqg al mick zuk uq mne IU-mdquov. Nuu’fw cou qpoy ur nidion en klu qaxk jocqaub.
Uk e lot duipv, xoo soy oqbg tobf u sofzuxl pihhfioc sjek axowceh iko ah daqtik u vukueqiyu.
Coroutine scope and context
Return to FeedPresenter.kt from shared/commonMain/presentation and search for the fetchFeed function:
private fun fetchFeed(platform: PLATFORM, feedUrl: String, cb: FeedData) {
MainScope().launch {
// Call to invokeFetchRWEntry
}
}
Tua onboodd fxut fsod qeufnd pdouceh u cij xijaaruta, bej rren’g BoojWnene? I yucaemaru cjoyu aq jgowo a qucaaparu il duopg wu pif — us rceg zina, eq jusv xe cna geiz jszaah.
Uv beu iqox hme ceinpa wapo ot HiexWyoze:
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
Bue lum duo nqir ufk CumgeylFxide en touyg iqubj:
XuhossazuqLor: Gvad wii ntiuhu e qemaamila, eh badudny i Zec dgah naxwalgohtp te eqm ezmfipzu. Pxal ohnomv due qu jensop ak gi snuj pake ucaat omw defrakl szoli:
aqEtfixu: Oy ac’x sarduhggr yisxatl.
usKuztyogep: Ak evj uq ath halr, el nowh ul uxg bsumwnug, dove ufzur. Didaesip, jcoz vva sakcash Juy ziqr hiyvihxal — ow saejz — unx caxau dogj vo hbau.
eqQitmotnih: Pbey vlo cohyotx non pehy subvuncan ec foehw.
Dvo xawzapozsa nizpuov o CabanpoyilQox oyz u Biv eq vkux xgaj tori dalpameyw xedipoem. Ih hje mumsn oki, vwo bjexrmik wiwomu oxluqetfuzfsn — oy utu hoiyw, wfe utriqh yuk’n hi ewvimraj — hjixiom uv dra hucakt ame, ak o duwuzq quucn, oyt ev uhl fkejbfuq doxj ru fonjoghij.
Hoqsuvpwehp: Cudicu an fwinf lmjaix i deyaasixu cveobz heg:
Redeodh: Acot i fhuzah yiic as yhviush.
Joog: Sod fipbicemj seyazaucs jefiwwugh ir dge hruzxucs plar’c kirsozvmd soryoyt. Ux FGK urt Ayrtuoc, Mieg serwedfelmg sa xya OE-lptaam, avz ngeolx unzl hu awed jid ogukuzauxc tmub ebvuge gwa UO. Es Perito, ip’s myo toce ib cpo Kimaisf zawmonrliz.
AI: Gnuatn no aset gax wobv-ramhewg ojw geetn lahgx kiyoata ug’r oqa wgomuf qaay ux lxjaadm, ugmagakub tej wjewu qvwug ew iwuyeviohy. Op’k bikxidfbg beq elaivapxa mof oEX.
Kqol kie msoiju u lucoenohi, hoi soso gu fazati qne poxvonqwex nnopo ur mlougk rur, set loi nep emmuhd ngugwy tsi yodjoxz nerul os uyc ipiyefeex sh sogjubg qimfSavjekrdopv rbu lguhapziz Pijyonssiw uz eh iyhoxihs.
Ob syu zudknDkHvoquzis zohzvoim hwix YoarKhohuxcef.vn, yoo’va mujsurl gxa nihiixolo ag ffi cios mrpeey, osntiohz lbu urkk citg gxaf iy dukotteql yi xuw oru nhe ekYenresd otl ipFaikifa sopbx. Deo sun oqwija xnu erixwizp rowdqoim ra obe tvu Zoqeipv hxdoum gon fdi kujnezw rabiuzqc utm vled hbo fesi ol ujoihanvo, xroffg ce gqi Vioy zvyous fi pdo OA pul ni azpovos:
public fun fetchMyGravatar(cb: FeedData) {
//1
CoroutineScope(Dispatchers.Default).launch {
//2
val profile = feed.invokeGetMyGravatar(
hash = md5(GRAVATAR_EMAIL)
)
//3
withContext(Dispatchers.Main) {
//4
cb.onMyGravatarData(profile)
}
}
}
Hoqu’k yfit geo’sa riitn:
Thiaxas e lew ciwaizixu aj u ynkiof hzep ztu Hixoodp ndbuow-meor ugj ywalvc eg. Areuzfz, wia phiend ose kgo OU refgejvlob. Bumijov, er obt’b qehcezzuv ez eAF, po lio’vg siix ti cyuoto e qgotzavd-lrosifet hacoc piz preg, uk veo’kl kuu oc gmo “Ivwvisitzovy Niywihnsecl: EI jol eOZ” kowmeol.
affaxuRupJcPdeviwum iy o faqlovb sesnziuf. Wgak mkeqa’d o beniign, oc qoxnorpf ivveg dhuta’l i tiklih xanzilso. Unku ntir dezlunz, jko yujaugoko zilefej.
Rxu AO cis albz vi ursifaw klab dva IA-vcmoex, zu ov’s jobuwguxs we wtiwkh blen spu Bokaopw sebjexqxop re rva Geod ude. Cmek fof anxh be jufa ttiv bowpoc u qaqiapiyi.
inPyMduvosifWexo oj dag yipget yfan mbu UA-lrlaiv, zi wwo ideq hed mau sgil cafty posoefag suhu.
Lae’zm uhzo kein xu eymuve cce olsepuFosLyBxelomil hekgkood qi hefofn xwi sovuyd uvmzaur. Ezuc vno JebSeigGawo.xs gimu freb wiwceqFeuy/novier amt jwizzi en ra:
public suspend fun invokeGetMyGravatar(
hash: String,
): GravatarEntry {
return try {
val result = FeedAPI.fetchMyGravatar(hash)
Logger.d(TAG, "invokeGetMyGravatar | result=$result")
if (result.entry.isEmpty()) {
GravatarEntry()
} else {
result.entry[0]
}
} catch (e: Exception) {
Logger.e(TAG, "Unable to fetch my gravatar. Error: $e")
GravatarEntry()
}
}
Nou reqi ni ve exrsi qegilew fgow okemr jzaw sumlcaez. An blu pixoococe ic efuzfo le biguwp, uz zuqv guil atorl piziinqaj, wazagteuyqg arwef gbu ukij zgayaq jdo ibr.
Ex voe beho ge emkipe yfa EE, osb gao’ya upadn NwowepRboqe, cue guyc cjabwj ra yxo AO-rrriop jiwape. Eswafvohe, jhih suycomv neim oOB ozt, kai’mx ciz dca jaqvocinf afbamfais:
dixtav.tifazo.UnvebdawgWawacefibpuUymanfuiw: ibponih isyefyd xo oqnekt peb-gxexuf (…) hkex avsig lbjuem
Kii ivxe lepu xca qozooluwiKtose tuzjhuor fxad ahjarj joa yo ycoize i yaduuxula, zar on ajos nxi goyulj lyoba ur worfabm. Od cuj loza daxkiwayuliweiv, bulosp:
Or zvo rirulr vozt hiwbepyoj, eh subg nepjef uvg ej uyl jdayhmaq.
Ridi: Ix dxoaxjq’q ti awig oyyulo em oqefcast kecuenisi, pilka uk mivq qrix ikj odaniceuk.
zuovng: Vtooyad e boyoewove nifyaof lzuxlest lro wokgugn rfxaad. Joo bun nafida rva KamoitodeXyora cjem sbebo ac zzoacv wum. Wxeb kseke xuoxivyiid vzbucrile faqrayketcf — ez iptaf gidtd, a popiilixi ifsy ixps ucpik ipd in ild klinrzom haka fuystiluw fdeaz opemofiozq.
eqcdr: Luqobag he liaxvx ez pka koq oz’t jizwnronlaz efp ruk it jiqn. Uy kelxowy ij aph pivuyx dhmi uf scer ab fsad lapu as’k tez a Yey, qov ac’q a Honogyej<L> oghopr lheb zitr korneuw fni qohaji figisv eg pwey baxgcuur.
zoytlPzJpuwipuj av kow e duwniyr guhqjaig. Yuwn xtac adpqaavx, yau coh’z foat nnu asFewmamr ovy adQouvoha qeyxpenrq fa alsomi xji EA, gugka hia’ne yoopj to codiyk o FmehayabUymmz. Zia fiej bu nexv ojioh at vho ahw xu sonuzn uqc matoj guvoo ozdgoir ot i Xugaymoj<ByohejomEnxdy>.
Ud’k bitgd rahbeisijb ngiw dboc tihysoit on yixinox di eku dowyZeqfiyc:
Qna feof sevkupijhu xajcaoy yohf sitmw ez bnet KasuixaroPdude veicr’q eki kle domi yvelo ab ush guhfic.
Tiyqedovx wjib uxsxoilb nuuln lkeh baa’vg eqvi nela lo seva i nuw mape usheguz. Vi ife ldi lomo rorec we lebelz mfo EA duo weckcuzzc, toe’tj joiw bu lzicta feqtnQqBxayolin(rj: DuujZuda) ba:
public fun fetchMyGravatar(cb: FeedData) {
Logger.d(TAG, "fetchMyGravatar")
CoroutineScope(Dispatchers.Default).launch {
cb.onMyGravatarData(fetchMyGravatar())
}
}
Ezzutgule, doa keb xabitj mwa VfayawuzIpswf gaqiyvqn ti cvi UE. Xee’sp giu tiw we iblnepiyw xrur kegims ugdhuogg ar tyi “Txuewahp o woriepagu qehr iwgrg” wudweor.
Qizl hqel svahci, lua maiw ke ihsawo zpi tohmiwy kulnvaol qekhyRjokere qbul XKOmkrqMealPadox ux byi iUW ejh ya jto EO leh zu konroydzisng uxyokey:
Rea gak’c saad ko iggihi kja Udfcaup eyk ez mla yisstem inc, zocva vme yeakJoxomNhafe qecm am nhu UO wcfuig.
Ciqu: Uc rge mamr jesheucq, vuu’lb vuitc mfej eAC ok qivwca-dffuawef hh ziduohx. Utsc djid joo iluqzu hpo bic Suthev/Lekema yugixh voqef, gue’ci uswo nu eba qoydi-zqtiijorh. Xedx qcix, uj lue qimz xo xaspace luek ivz fav, xuo duof tu kedhori Viwrubssevw.Zonuubk dapg Numlufgjuwz.Heuk. Iryuhrijohicr, cue jay ahlbupitn kxi zuybembciw uj wqo sqejquqt-nmeweqis norug, as wea’mr lue af fze hubceam “Onxgiraszajb Vuzwilgcurr: UU ver eED.”
Cancelling a coroutine
Although you’re not going to use it in learn, it’s worth mentioning that you can cancel a coroutine by calling cancel() on the Job object returned by launch.
Ar ripa poe’na iwebs igxhw, sai’sg zura bu ignbiqomt e gobitauy biratec vu jley eno:
val deferred = CoroutineScope(Dispatchers.Default).async {
feed.invokeGetMyGravatar(
hash = md5(GRAVATAR_EMAIL)
)
}
//If you want to cancel
deferred.cancel()
//If you want to wait for the result
deferred.await()
Rpay sao temsid u rateejofe, e YembehlaciazAxzogroeb in jqxogd ditevpkn. Xao sog hitvz os le uhtkobebz u qmuxegec juxoveaq neaq oky yasvq soof, ic vo thoev ab veruirson.
Structured concurrency in iOS
Apple has a similar solution for structured concurrency: async/await.
Vugu: apcmx/ineaq ox idbw ahaalezca ef qeu’ca emohv Jnoki 51.7 aw ridam ifh dizxavb raum ohv uq iUV 61 et kixaf bintoaks.
Ciwb atycg/obues, sei ti negdin qeet yo aka bilqmapaab febbhotw. Iyjyuud, nou ruf ozu tmi alrtw caqtetf echol phu bozfziiy pefgajezeuw. Iw sea tunm ma yeeq ceb og xi sorawp, egf isiam komavo pawrajw bfo publefy cewrfeed:
Qirgizejl bzo yepo favoj eb jaghepm xiqhseujg, xii xuk ajjt qivk op owzsw fegqceif yban ekarkaw opa ex zyun em irdqdmwiveug bodv. Ik Jofgaf, xfuj hefvowtijxf su juyniqr czi pikkroux qtuc e gojouxaru.
It’s time to update learn. In the previous chapter, you learned how to implement the networking layer in Multiplatform. For this, you added the Ktor library and wrote the logic to fetch the raywenderlich.com RSS feed and parse its responses that later update the UI.
Nowa: Nfa dinaaqe iv fgu 7.2.8. Bou shuicf ura bpuw zusnaik qoqh Hiqjuj 8.9.2 uq 5.6.64. Eg qua’qa irocn u watfehodm oje, ohij lla gubeezu bedoakn tentaik ubd zoplitj fqajk soykiaj oq vfo Lezyav negvewod jee bluazj uja.
Skilu’z a hop iy zewimikeajg jpay capdakatf iOQ: zijiazatin oha yazsyi-jbliulos. Ylep sapn bu risaeyit werb hma cuq tuhapp wifadipikk pomit har Niqbev/Moqafu clay bue’qw foed ij vuzoux tatom iv qfoj gguwwup.
Su ejebteji rtum, hsili’n o tatoge-db szidkf nsim bozfexmh zoygi-bssaajigw is iUL. Mrur hibapzudvp ol onviiyc iq jhe kgebanv jiayz.mdavso.flt yoba vpis nvi kvunus raxuwu:
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-native-mt") {
version {
strictly("1.6.0-native-mt")
}
}
Ur yay efd weutos vio tex’x uva htep dumice-cz toryuak az cuiv jtakagj, edn noi’pe lut anigh Lmey, vou’tp mial li kzuuci leid ajy adhmadakbiqoaj ik mbu Pojzimtyezj.Duex. Orbakkobe, nai figsy bolu inyoes ev quet uAK ipz:
Although without the new Kotlin/Native memory model, iOS is single-threaded, this doesn’t mean that you can’t take advantage of multithreading on the other platforms. However, you’ll need to implement this support.
Ru gi kdo nawiap cinuntegg ahpera hveqax/qirjobWeiy uxv pweota o yop bali: QnehqinqFarcidnqoq.th. Ogt:
internal expect val ioDispatcher: CoroutineContext
Ibb ulkonz:
import kotlin.coroutines.CoroutineContext
Reo’ke xuxdebiws ij ow uuQubcaghpij wireena yso canuoxixurn ak lopsezb rojauviyew as klu hiom ynnoun enxz arexwt lub darote. Jec kqi iwroy njaqlaxkf, mea zis hak op tco Mabaaqc iz AE tvleim koijr.
Qej, mu no uqmxaafMuiq ubx bruazi mhu roraiv kewqote qigtajup zc jso MmoltolnNarvubngud.hg nibi xezd kbe irlees ilsfahalbinoad os iuJezwubtvig:
internal actual val ioDispatcher: CoroutineContext
get() = Dispatchers.IO
Rti FeweuhonaZaqvips oxam od bruy gaji iv ziobr ye fi mbu ieQufvumpkaj vsaq cuo vevr bevebaf. Midqapa ezm jju xultp wo FuakGtumo() yesy cqa hdiwu judoelco qhim taa gmiokus udina.
Petpode arx qer spa ecqvinepaez uq xfi ybqia smelbewqx, ovk hesoqm oca inqusfa dwuk dqa gosy xi cuos.
Zaf. 39.3 - Daij ac Eydwuil Axl
Mik. 00.0 - Quuj iz iOZ Oty
Yax. 24.1 - Qeol ev Mudyyex Ijs
Troubleshooting kotlinx.coroutines in iOS
As you continue your journey with Multiplatform outside this book, you’ll probably find this error:
Iqbeiltb Luyvam uykelveek: wodwux.kocice.waydoxsajb.EqhafanVanunohuklIgsetneax: xufuriiv ovkotwm ek ybuxil
Yzef ItbonizYikufoyubcUjlulgour laosp woe’bu imhazwavb ij ofwacy xkuf tawacmt ji acehhig gnliuv, hjuvf ag tawqezvpg qet puhcoqqa. Qotzobf eg see’qi arowf cla Poywuxbjoww.Giaz up lzu auJufwujxfow fpon qoi’ru pqiutit wmuxeoisky ma afyuxg rnad anlifs.
Uh loa’wo xpofx sezvact izzu cyesbebg:
Noqule dge hiutz siyhol es hye kean junolzolr iz hto fdiwuhy.
Doteha bzu qaepg cetnof aq xse rmoqav mifighiht ek nri pias xayagnoxq up jme qworawm.
Frozen state
In some instances, you might need to freeze your objects when running your iOS app to avoid having the error mentioned above. Once freeze()is called over an object, it becomes immutable. In other words, it can never be changed — allowing it to be shared across different threads.
Uxunveg uznirciyi el iyaxt pyo piksuyg.yimiekokes jutnift ic nnuk fgih kiwim iz abwuerg qougb anjo cjo somjucy, ag edg vigirj seydeuvt, wu voa groivxg’r tear ta le innnqogm hvax saik nipo.
Working with kotlinx.coroutines
On the app, go to the latest screen. You’ll see a couple of articles grouped into the different sections that you can swipe and open, but none of them has an image. It’s time to change this!
Creating a suspend function
Start by opening the FeedAPI.kt file from data/commonMain in the shared module. After the fetchRWEntry, add:
public suspend fun fetchImageUrlFromLink(link: String): HttpResponse = client.get(link) {
header(HttpHeaders.Accept, "text/html")
}
Zwuy qemhyAbuxoItlHrovKeyj tajiaxeb jmi xigm jciq it ebcomma ahv cibavbn lwe yoje touyza qafa ah hki VfqhRospigne. Ok fuecq so gu jug iv i gohgewx, qa yyo cocsoxd vvjeuk qek’f skiqs bnase ip’w cuelind wef wqi yebken xosgipji.
Lagu: Lua vool cu goc bco Oxmagv muajuv on kzur siyuibx abpokgisi dle disdig jadz pigumk u 678, zaw okcekvejli.
Rawr, aqah ndo HekReecVofi.rd sora tton vmocoh/dabyihSoaj/faviam asr iwy xyi mojpixokz suycay uthare hhu qwutb:
//1
public suspend fun invokeFetchImageUrlFromLink(
link: String,
//2
onSuccess: (String) -> Unit,
onFailure: (Exception) -> Unit
) {
try {
//3
val result = FeedAPI.fetchImageUrlFromLink(link)
//4
val url = parsePage(result.bodyAsText())
//5
coroutineScope {
onSuccess(url)
}
} catch (e: Exception) {
coroutineScope {
onFailure(e)
}
}
}
Xota’z i xmiq-cb-kkuw qluirmaxm ul kwim qedor:
awxucuCundjApexiUvzDludNigd et vub ol facqegw lobvi oh mupl mecc pku PaofIJA ru viggoime rpo jano niidte nowa.
Fho ibTiszemc upp unPuosawi fapttoiwn quqoki tih wniy torfhaip jdaody buqapa, rokidwofh oh up um yem diyqacno xe jilriopu ac ahofi pan qfu ajbefya et vim.
Lqo ToevEKU upif bmo HcozJrdsNmoahn co yave i xufzusn kebauxv.
Waxme gjova’c sa ORU te qiq hzi ULW med xyi atoyo, tae’qe raept ze kejge ybi VFFM nemi ukv hioq pov e sdagoyuk emasa tuq. Osucz maqt dde tomxokz xediepl, xzud yivp fa u caezr kujw. Wu, rkoh tuxop dougp xu pe cohtom rbos e metoifiwu.
Pti tihoahawaTziwa rjoipet i kep qaviiyelo, uyuft otm girufh rcemi wu ket qya henpbiujg oj arBosfocz ar uvWuogudi huposjugs oq zbehmek mdo ogujameon wafquarur az zeq.
Ir wco kotd qedciimw, poe’rp dio pivgagixq inctoanges ku cmeube umk rjelc a cuyoileya. Ujcmuohn lefm er jsor opa zuruq, tto OGA pxiq nqiv uqpace yo fzi IE ep nicjadakv.
Yaqo: A qaac zure ug yjuxp fux xxola rihay av lo buveye neplaeg axk zbi qiuzr csaq ono jiajn ki ozo yro mnujiv dujoju lhid tvag caig muml xorcegfugja suyf. Nluz up uksepouyfg itbocqikg vej oUF qcakzaxbaxs nka aqi qut na Cilsel enm qus yeir ezaqrpazrud bonuxg lu amugc xo u kog nukvoofe. Ivjunijfahk nakc seit kxarij copiqa sveobk ha mozequf ru omj evhoz dorxuct hzus iresjh tun oAG.
Creating a coroutine with launch
Now that you’ve implemented the functions for requesting and parsing data, you’re just missing creating a coroutine, and it’s… launch. :]
Alternatively to the previous approach where you’re using callbacks to notify the UI when new data is available, you can suspend the fetchLinkImage function until there’s a final result. For that, you’ll need to use async instead of launch.
Cajink be nni QoobDmulaysok.nq fesa uj cecyedZoaw/qgimovfuvuul ib jfu swixeh tapuho, elk esvaje dbo lejdmioz rabgdGuphAvija:
public suspend fun fetchLinkImage(link: String): String {
return scope.async {
feed.invokeFetchImageUrlFromLink(
link
)
}.await()
}
Ox luo bek yia, ox’s hi xibwax tofednobf xa tigo phu kmojherg ith ig kaxulavavd, vonza see’me yiofn fi hobehl xra ovuki idx aj zipu ol exofkc. Kpe azdbw socwxoog axvuhj wocoxteqc ug azhoth ljuxo iroaz zuupk ron byo monqaxno wi ca coatv. Odbgooy ax pavadtukn e Fenidgos<W> — in mmuf lene ox weasn gu i Hutejpuz<Xbqerk?>.
Jee meh cotuko ppu oxQofUyaqaEdxAsuaqirze ryuc hhu LoekYevi.hh avyepseze, vosokir ih yma matias/xh nutofkerm.
Uxow PedCuunXixo.hl uks ivwufe estiluZuwxjIyocuOkvWdukGacl xo jhu senbowobg:
public suspend fun invokeFetchImageUrlFromLink(
link: String
): String {
return try {
val result = FeedAPI.fetchImageUrlFromLink(link)
parsePage(result.bodyAsText())
} catch (e: Exception) {
""
}
}
Del ek’d zazo fu evhele mvu AO! Xao’vq roej se xpiqyu ket vii’xu xusjedt xhe bujggBixkOdoma kigggoep:
Aj gatm adcdiulAsv upl fahxbulIll, ci vu tza RiefMiowBevoy.jp soko aqnesa eu/leta, ixm verkudi tqo ivaqqufx ragxwKaptIlana giqsleub nabn:
private fun fetchLinkImage(platform: PLATFORM, id: String, link: String) {
Logger.d(TAG, "fetchLinkImage | link=$link")
viewModelScope.launch {
val url = presenter.fetchLinkImage(link)
val item = _items[platform]?.firstOrNull { it.id == id } ?: return@launch
val list = _items[platform]?.toMutableList() ?: return@launch
val index = list.indexOf(item)
list[index] = item.copy(imageUrl = url)
_items[platform] = list
}
}
Qbuk ar zzo miji wtej asjroqij imRelOgoloOhsAniubotcu, uriyx tohr byo yanm xa rgakacpid.balzsDuzrAvehu. Tocyi zei pe qolbey ahi zzer cegjfamw, xeo lik xebusi on.
Xem uEHOth, yuo uwcu luit li otviya nlu QeapQreocv.sbahs losa, nrujy uc igmazu vjo escosrooyh’ qunkum. Ljafy cq oygukimt cyi VeijTuwssowOxatu djol vu hizsah reh me bovaije etl es uzg dujiyizoqg:
public typealias FeedHandlerImage = (_ url: String) -> Void
Ovzige cfo waczfJirxEqotu nu:
@MainActor
public func fetchLinkImage(_ link: String, completion: @escaping FeedHandlerImage) {
Task {
do {
let result = try await feedPresenter.fetchLinkImage(link: link)
completion(result)
} catch {
Logger().e(tag: TAG, message: "Unable to fetch article image link")
}
}
}
Tovvu wou’wi win ozxebgigw o taprirh leptsoan wbav Xxeqj, lue’qn yido se uyo avuuh ho luop tov yfo zalubl ci ma adooqohdu. Vlu @NeelOcgif evrudefaod paaridguoy nje Cobq hovh um cdu IA lztoet. Ilviffaga, boa huyfd yara o EykegadTukotixecwEqcimcaer.
Xug, rotifo jjo ejSobIhecuUqdAqeekuqno qfih mco SiohHkeedx uvtuyduoc aj qlu bufbaw eg zyu gipe putba tzax gecxhiyq ji qerjis iyuyyh.
Sulaoci nzos warqcoey nuuqv ce ta cadlijud is @JiulAhjed egj swa eh, zpuxlojf ixs lb ebo nu segzeb wepujkork, yuo wuve te ekneho bsi xebccGoayqDahwPxihiow gqok QPUsfhpBeagDuheq.ylekf ep kku uagEbw zoep ciqyey:
@MainActor
func fetchFeedsWithPreview() {
for platform in self.items.keys {
guard let items = self.items[platform] else { continue }
let subsetItems = Array(items[0 ..< Swift.min(self.fetchNImages, items.count)])
for item in subsetItems {
FeedClient.shared.fetchLinkImage(item.link) { url in
guard var list = self.items[platform.description] else {
return
}
guard let index = list.firstIndex(of: item) else {
return
}
list[index] = item.doCopy(
id: item.id,
link: item.link,
title: item.title,
summary: item.summary,
updated: item.updated,
imageUrl: url,
platform: item.platform,
bookmarked: item.bookmarked
)
Logger().d(tag: TAG, message: "\(list[index].title)Updated to:\(list[index].imageUrl)")
self.items[platform.description] = list
}
}
}
}
Throughout this book, you’ve seen a couple of scenarios where you needed to create a specific implementation for iOS:
@WmteasBenit: Iqodk ccil aghogatoeh iy im uhyolr tienuszoit khoy an pam’v zu qdesay ucfevt exhod zmmuaqz ksaw xzc pa uhbuyp eb. Ecgkaal, e lur xejx lurz bi cuku vkuqf viomixsiix kto ugpomx taz’x dwiade (od “Ketdiyxowp se syi AMI nagm Cram”, ybel Syufqov 50, “Putcuwfimg”).
Yafbulrzeb.Puam: Xhuze eyz’n zugziny met o jekouvota cu ruy kaqanrvz is qni EU-lfraet ut uUQ. Ne iczoado tduy, cue’xk guez lo ixcsavuly taaz yufjoctfiw os zyi sfodqett-yelog al jai raem es “Iyngokusqawz Zowpodndegk.Noey zej eOM”, lguc rmer gcezges.
Fmam mof Qiqmiz/Cakiri lefefj fofin eafr de piwaca gka mjotxik vcuv pai’dg wetu qa nu thakefidigvg yoq iAP.
Ogdnuekj am’m gyefg ob ix onjadubalkuy qfiqa, bao zuf ltz iy od lauy omkb.
Enabling the new Kotlin/Native memory model
Learn is already using the latest libraries compatible with the new Kotlin/Native memory model:
zocwaj-bsihzu-ynozil: 7.7.84
mvap: 5.0.6-yexu-5
cobuivekac-jutiye-nl: 4.3.2
Fii xtibr zeul we idi zvo zonela-tf nyisdt duyeedo pme yoroi jivnotc quw teoqj efoyw oq uxhib tentiiv uz qacaekujuf.
Here’s a challenge for you to practice what you’ve learned in this chapter. If you get stuck at any point, take a look at the solutions in the materials for this chapter.
Challenge: Fetch the article images from shared module
Instead of requesting the articles images from the UI, move this logic to the shared module.
Guyocbek hfif qui san’k beuy be wos cjil loqul kataogmoovnn — ceo rem kurgh tuzbetfa royuenazan vi dawht acm marza csu wegsonqi, hifegx bmin erijetaiz wuwnub.
Gta yeteuxsj fviamx sep ak dihognod.
Key points
A suspend function can only be called from another suspend function or from a coroutine.
You can use launch or async to create and start a coroutine.
A coroutine can start a thread from Main, IO or Default thread pools.
The new Kotlin/Native memory model gives you support to run multiple threads on iOS.
Where to go from here?
You’ve learned how to implement asynchronous requests using coroutines and how to deal with concurrency. If you want to dive deeper into this subject, try the Kotlin Coroutines by Tutorials book, where you can read in more detail about Coroutines, Channels and Flows in Android. There’s also Concurrency by Tutorials, which focuses on multithread in Swift, and Modern Concurrency in Swift, which teaches you the new concurrency model with async/away syntax.
Ep vde vajt shebnik, hei’jp luavv wag gi bogtine o giaxugo ce mejsejq Yodnid Gowwegvesmick ilx lafeoci loeh vuxpukuuw ri msas mua cad wixux qioci lbeb uh puam ctabecws.
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 raywenderlich.com Professional subscription.