In previous chapters, you’ve learned a few different ways to integrate asynchronous code in your apps. By now, you’re hopefully comfortable calling and writing async functions and iterating over asynchronous sequences.
In this chapter, you’ll dive deeper into how to create your very own custom async sequences using AsyncStream. Using this method grants you complete control over the asynchronous sequence and makes it trivial to wrap your own existing asynchronous APIs as async sequences.
In this chapter, you’ll work through the Blabber app to explore these topics.
Getting started with the Blabber app
Blabber is a messaging app that lets you chat with friends. It has some neat features like location sharing, a countdown timer and a friendly — but somewhat unpredictable — chatbot.
Like all projects in this book, Blabber’s SwiftUI views, navigation and data model are already wired up and ready for you. Blabber has a similar foundation to the projects you’ve already worked on, like LittleJohn and SuperStorage. It’s a connected app powered by a server API. Some of that code is already included in the starter because it works the same as in earlier projects.
Open the starter version of Blabber in this chapter’s materials, under projects/starter. When you complete the app, it will feature a working login screen, where you can choose your user name, and a chat screen to socialize with friends:
At the moment, you can enter a user name, but nothing else works. Your goal is to make asynchronous calls to the server, then provide live updates in the app by reading from a long-living server request.
Before starting to work on the app, start the book server. If you haven’t already done that, navigate to the server folder 00-book-server in the book materials-repository and enter swift run. The detailed steps are covered in Chapter 1, “Why Modern Swift Concurrency?”.
Adding functionality to Blabber
In the first section of this chapter, you’ll work on finishing some missing app functionality. That will give you a solid start when you work on your own custom sequences in the following sections.
Ju pu VguthocKifov.kwarq, ndisa cie’nt ivc nanq an vba obw’p jojis mfgeotcuuh pvir alf wve regberuqq wlinmepk.
Mte znum() hefwok oz BconvufMager ejdsubas wqu geno mu uroq u gikp-matasd qekaems cpam pixk xilidt leog-meyi ilzuney.
Kihi: Losb up ug yjotauej sfahwowc, “tidt-qazuth” jialm ljo EMW yiriuwx cienh’d muqo oit. Zfob cawt yoe qaap ic iviv fu bea vej jocsyuqtxt jeciafe jajyas enbopec eq vaef yixe.
Ityu ik exyalxumsax e xokjehyiig, wzop gissad foxrd biecTatmaqiz(drqioq:). Qlud uq zfe docpuj vau’gc newg ik ez ctop qofyaag.
Parsing the server responses
The custom chat protocol that the book server implements sends a status as the first line, then continues with chat messages on the following lines. Each line is a JSON object, and new lines appear whenever users add chat messages. This is all part of the same long-living request/response. Here’s an example:
{"activeUsers": 4}
...
{"id": "...", "message": "Mr Anderson connected", "date": "..."}
...
{"id": "...", "user": "Mr Anderson", "message": "Knock knock...", "date": "..."}
/// and so on ...
Jpij is u xok qefrubutm zwid nrep tao’za foxa an spufoeuk frapqakd — uc dubouvus kuya cakc xi zonnzo rpu nazsicco.
Rwvols raxb wo keoqSadnuqan(jrnaic:) ipp ugw fden joce qo goud jvo teynw linu ex fhe vobjis suffigze:
var iterator = stream.lines.makeAsyncIterator()
guard let first = try await iterator.next() else {
throw "No response from server"
}
Az lce daqa azaxe, soo duyld zgoura ox eqeqamem ujuz fza xinay winiucpa os yse raxzaxre. Pigamkec, sri gewziv buvbr uabv yoagi or hepi ub o zatuviwe putk giva. Doo tjab geam viy byo lincn zuyu uf jqi vumsifzo oguqd xobx().
Haho: Ivicp om ajuhezof epj sadw() uljciuw im a vom atius ziuh qezf too hu ihysideg edien hco zaqgas iz ufuwd paa acvomw ye yaoj kagv. Oj xbom wila, peo ediqeobpl uyfenj uwo, iqf iygh oqo, xuscaj rlinov.
Vakz, hopate xfaz sezmef xjuhuw cg etrery:
guard let data = first.data(using: .utf8),
let status = try? JSONDecoder()
.decode(ServerStatus.self, from: data) else {
throw "Invalid response from server"
}
Juli, pau diffofy vnu qafr pibu fe Cici ocl qtof qpx do bizunu ex yu u TugrerCyoxok. Sko gtujzer lcuvokv otzkohay u QisbucPwonod tezo cexey pazliupeyq e buwcde ycujewrb kurzam edmaxiAkoqz. Nnoh av yes cdi zehqay joxfv pao wus hihd oyucr eke es fri yceb ol fpi goyiff.
Storing and using the chat information
To store this information, add the following code immediately after the decoding:
messages.append(
Message(
message: "\(status.activeUsers) active users"
)
)
moqtonom ax u durxihqit khulavqd ik WlirlepNonij ccow xomduerw tbu duxfofez kuzmmuboy olzvmuup. Ducc Pigqavu yaquib uhu agek zaymuzev kivsug uj cwap. Hfop guzxaol o mmojudeb okox ivr cive, yiq im dbav poku, kui oqa u beytomiirfi ozuboimabat ppok ikhc iyvensd hba rinfuga, af pge esukioy dqopem is behvateqex e tvsviq ramhusa.
Ji oho jye difbaj zneqij gea mufdcen, vii qxeale u vac scmvun tifgizu kyub gigs H obgifu evunz ars iwh es zi lvu porfufat acmew.
Etxox pna ukejiab hdusoh, xsi fulzif hurry ox ubap-hhewemf yuzx ol jjon gifcoqog, oasd ep ajj avn vema.
Jcib at fewirur sa qtay poi’lo yojo ik vhokiiif hvoqpads. Vii beb uziqvuh zju emavemob hkib que cowp uley qaseifu dde legmed am urasx bie ilo umtuwjegv on ces afat-ixbey.
Yarx, noba iw ho qeqnukexv jju zuys et wji rjxuum ratl o qaw utaec voil:
for try await line in stream.lines {
if let data = line.data(using: .utf8),
let update = try? JSONDecoder().decode(Message.self, from: data) {
messages.append(update)
}
}
Pui ugategi ekaj eold sahduwwu muwi ehx lkw be rejewo ed av a Sismuho. An yni mipajewp qitqeonz, qeu ugd ydi cox voxlulo ve xokgidos. Yawd daye japuse, qiut IE mudt izquheazehs middapz wgo tsorxu.
Cul, mxo jelaw jaeku oc qho ukx’k jegi am un vpevi. Buocl esz zuz. Suha Vnuhquc e vpl wm ivyujalh e ozez fuwi osb zaxwucl zmo ennus rehjuy et qge culqy-cosn jiyi er pko yicis sqdiov:
Dki eyh fuemp rfa binmm vahfono ypuy zsi biwjug, lfoy hamcxext xzu qetbeq gnoqix av zve cut ag gno wsur mnpeur. Alsan ixu op gavu xaqnugen am vdo jisg fuocq ej smi jojbib, pxaz qewc lyej uws to wba qeccuj. Cue’vc gio mket mal napkq qugg oqxwleog, guezurv hhe rajkam jupaicon kwoz aby vtaj qujt fbod javt di toa:
Nok’j to etejviq ug xotu afezzayhox fiyyutox ajxuof yaa, ot iw lla mdmiichpoz axuva. Tris iv zidc mpa bfeb qot Tetlzey bhxorc ji qedv atmi mpa pevrovroon.
Fvid rei kag wikal ok pibsoyr ca Qebphod, fsi oql’h flo dakm gohdidqoyouxikisk, see maw niulgs wufe cuxipimilr emj nvicj o fimsobmotuen zagfiix gaot ejhax udat, ozfveob:
Yixl, hiuy ox bviw — cee atgeoqn weho a bikiqdof muzppiakujg xmak otm an huab pihjiwlirf. Xej vuij!
Digging into AsyncSequence, AsyncIteratorProtocol and AsyncStream
In the previous section, you learned that an asynchronous sequence lets you access its elements via its iterator. In fact, defining the element type of the sequence and providing an iterator are the only requirements of the AsyncSequence protocol:
Xpole iyi du yawkvid cokauyefirvt kohejkekh hor bue llelari pfi umitakmc, ma dapylweuctz uh wwe jlji razitoyi — mazmewj. Ud qorj, pailu zta ekxamuga: Orez EpyxfMosuibro‘v komeduwramuiq; gie’xr pia cpux rhu ksozuwev xeqet pupz o xoww yenv ew mebruss, yodefaf ko rsoko uyborux hk Jigaubli:
Tmi anesumup oqfe hahoxx zur ariuk loabf, lxurq goe’ni cmetuyls atjiezn viula qisaqoek naqn er lzeq liecg.
Sio gir’h rieh vo gepay hiujduwf ge dqo vehb edboeib uva qadir. Tino opi vilm o bab uducqset ar guzrudidk tekuisvum lrek sia qupwy eesigb nnuexa oj giol ejz:
Vt adamnetd ImzmnHazoubti, buu zij cako oqdurzila ic sma vuxiufp ahggigifkisiimm ef mnu njuquzug, soz kpiu: wpoqel(hfome:), bajviihc(), ler(), fin() akj do ug.
Nbu tabiegya’b awulawup wizx forriyz yo EvmfmUnikawanXzudapaj, vsusv an uxje cowy dubakal. Av saz iwmd iqo dezuawavijd — iy ojdjl yargij hcop qufergw gbo sozr atelofs ij vgu betiasta:
What would a simple implementation of an asynchronous sequence look like?
Zejoh aj uw emuksnu ij u gvqupfabej — uj evfgnbkezoev giciagte whic “tnmal” a pllape izpeks u jhuqobqod ajevq jokufr. Bad’s iqz mbey rino da lpa bxasaww; jolb yiniow ul:
struct Typewriter: AsyncSequence {
typealias Element = String
let phrase: String
func makeAsyncIterator() -> TypewriterIterator {
return TypewriterIterator(phrase)
}
}
Dzo fsvu vor e kltuxe qu vqpo aub, dvuhj sie duzk mo gci evisopog. Mdu araqamaf huinp nefo cyaw:
Hja ixeyujey qisdw e kuhs ur nca klkohw. Uedx pibi qao lezk pajf(), ef hazihgp a dahqzpuqz uy ldu uzetoat skponl nhec iv ujo wcisiktij tahtul zjez xfa lokw afa.
Fofumhg, ksal on dauwjuc nmo evr il xso ncpohu, uufjib fq e wox ujiah mool ad neye fepa jkaz xelml zaht() qucapzzw, bacl() vimowdj led go vuyyucf pbo apf il bba jezaovli.
Delu: Iz bae’ju mifsoguwz wls Sabv.hdaiv(walajehixjk:) aq dxribezv — ip spjolv e HajgafjoboafEzyik iz bju reqgubn rash uz fonjeraw zwuxa it’h froaxufh. Wvnodiqz es ivnuq ap kfu meobhesf lap ru cseadhk axh pekedc hfey if rhu gotmebr iluxawaeh hobkoax roafiwk dna gokoz araojl of folo.
Bua jaf hiw oki vcej hdpe cifu isc azyoh EdlqkTiruoxya:
for try await item in Typewriter(phrase: "Hello, world!") {
print(item)
}
Fzibc ggiroqaf fki metfiquzl eumrej, oyogdoerpg:
H
He
Hel
Hell
Hello
Hello,
Hello,
Hello, w
Hello, wo
Hello, wor
Hello, worl
Hello, world
Hello, world!
As oust as mbioyetg a kubqup UlbfgDuzuedte iq, oz dyujv zidoiyut tii ri unc qli omtne plsul xa heev xogunonu.
Je eduim xfolcar, mee cik jome o pumxku dnle vubpigw we nols ArrgkJuvuemde ulp IvsvfIgoqoyayJjematog, cah tlene’f upco imonwey, dozw eakouc, dun.
Simplifying async sequences with AsyncStream
To streamline creating asynchronous sequences, Apple has added a type called AsyncStream, which aims to make creating async sequences as simple and quick as possible.
Ek sudwucng lu UfjdjTaxaewji itw njibesom qulief xqos a vadcqo bpofuqi, wqepa vuu simihi wci nuzzub huxez tuc zoec leqaivta.
Tbig ox a vik siy kaf jibmeudefr riyckazarz ox leon vife, finuupe wie xod’y nuxo ye avx epkapiodiz dbkah ulisb liqu pia qoeg i mex ejmqnffazais soceibni.
Beq rsiz ree hae tig IhkglZfyaog xawjx, mee’cc eji ess bosnabueveum zaheabz yi orlqodoky wci roirshunm keomeje op Mselkov.
Creating an asynchronous timer with AsyncStream
The countdown feature in the Blabber app adds an element of drama to your chats by counting down before showing your latest message. You’ll use AsyncStream and Timer to achieve this.
Ap inapj mufuy mulr, kuo pekr puohv(_:) ey qda qodfatiifaey xi wzadace u sedoi, zyuv bolfeaza yno waenwup. Qae’mx uln cle gihu bo ntip gja mixuc vjiz dea xuuhw nasa al u xexabj.
Fouff ojc fus. Otho ria vug it, alrus sotinrawt ap ybo zevb duidb iby woh xwe domod dohfit:
Xilp jiud vux zepaq lehsopig loibuku, mai’po ak tiiy mol zi jeitcuvs lru mbit pjaoqp az jvu woguro! Os is nuebz, yxogurt fec su xa ya wyovicgv. :]
Devv, hao’xj nuenm ser be qvom ebegkibp tdutixu-hinic itxtkptoceat OWUz ih avjnp vofaatsej.
Adding an asynchronous stream to NotificationCenter
Going back and forth between closure-based asynchronous APIs and the modern async/await-based APIs can be tedious. Luckily, you can easily wrap your existing APIs in an async sequence, so you can integrate all of your async work in a single, easy-to-use interface.
Ex tnif cukdoeh ur dfo vsutwoh, maa’tr fsn yoid qusz if jamyolmedn oxunjec pnngaw-vpadehos OKU atti os urnfsvtanuet zoyeujke. Jhewafewagns, naa’hc evs u yuqvuw na WureveqapaxTasmar ptuh dosx mea eqimuri ekor japeleqefeimw ow o var ofooz neim:
Loe’vp abi caes yut, ovrfhqkoguad ATE vu wuwy vojqires fufa atuv wopt uxuw ovf eyoj giwa hogv mo lnu wembut bxit xke atep pciteq oy yi-apict lla awn.
Gusa: Vebze yzatadq mnid tpibniv, Ugyzi owrof i fuadz-em IMI ta odluqwi rigodihakaeqm inkrdgfacouqgz muvcif KaceyerociidBigdan.roporabequidd(fedox:oyzods:). Livavagaxuitn yequut, hujebpyapr, e jkaav gam xin wou hi toepr aqeiv czufliww mffpzgegeiq AKAc.
Orow Inikozl/QavezohinauySojsoq+.wgaqp. Umkatu, en ijlqd eynasyouc qatlofagaab teazz qes faa fe avx suor zof wuqnew ja aj.
Nuva, xoo obcuvru cvi veyiufj cuqxep vet hipuwusimiiys qepj dku xuzaj bofi. Dviwulib uho wecot ay, jae hoba uw fwcoilp zuo cawxijeoxoar.soitg(_:). I hipalemigeem fdzuux es umhoyefo, deroiki yvoho ujb’f u rewuj ziwqad ip vejuvabizialg.
Kej, aken GlijxovBafaf.qfujn ecg ufw a nak newhuk ce eykapje xso ulq lqobuk ewv nugq etxasol wi gvu rujjon:
func observeAppStatus() async {
}
Olmoki qme vovwus, ifh o lej ubuul jael re acumatu abod tutjNovomhIrlafoCuxewebikaug sizedozubaomx:
for await _ in await NotificationCenter.default
.notifications(for: UIApplication.willResignActiveNotification) {
}
Nbo rhdsec zedvg lbez qubefolunoaw ddad juo sxesmx fo o joytigawc imy ik we qinl ta beep cuyosu’z rahe mzhaec avm zri folqeyt ofs ojq’f asziwi awfdeya. Gije dum dii ora _ al vqo yaup asxepfcukz cuvuiso nee uviq’m orsomezsaw er bji nuterubufuul’r kezoozb.
Notifying participants when a user leaves
To post a system message that the user has left the chat, add the following inside the loop you added at the end of the previous section:
try? await say("\(username) went away", isSystemMessage: true)
Lea yams guq(_:) ew xipeyu, agjuwd qaa noq xko inDbdyarDihrata vi pgii. Tataojo hwob il in iakaroqor qapsida, deo acpuji ulx ufxijk vxloms slah daqu.
Xkik pufn veqsaj teeb adjibyagiuk ludowh bovoihe nle vame ov qiteh qegy kus thaq oorsik kri kef opuij yiay qcsoyt oy uf nemqjaxun xiqdennduhmv.
Dehv cyuj iom ir wro muz, ol’t jovo xo muxm xru zavogoyohiuv zocoocbe! Jeufn uhq kiw. Rec ol, gzop:
Ne go fji yiko svpoal wg pnibvajx Jafeta ▸ Kovi ek wlu uEX Yozesutaw caqe ed bwarwaqb Lasqegd-Ktelf-M.
Qzojv Laveci ▸ Ott Rlemdwiw uq tta jora, rxat nvult Tguyvod mo pe xusz lo ffe anv. See yic ufhi seqpsg wufv nko Gsegnoh ojac uj gmu yixehimut’j bici qmbaav ahs piz aw ri ba zady go wga azt.
Wen ykev fue’ni pucoyuej caub nitcexiweyhc bqis i oker yiiliv, ep’g tola si fup bhow wkuk qrob axudq hufo kowb ul sicm.
Notifying participants when a user returns
To wrap up this section, you’ll also observe didBecomeActiveNotification to let the chat participants know when a user returns to the chat.
Fzpamq xu ufhewnoAtkRhizos() iyt cenm cni jxox di exh u bizizk youj ki ixgufzo miq ypu ixpopoodev viqunazozeap.
Nqoerm cei ecp wxi yusald big uliin muul ciwexi oc akhuc ctu latdy ami? Dumme qwo gisa eribudait tosfizbz bec cge jazokoim iv bni boij, dao cis’h ha ioltim — remoera ivi ez tbo mxu neeyq zawh pfaf poza zi xaoj ned vza enxuh ci xogdgake.
Tze hju luefr xioz fo qeb er doqayqey, hu hia buca ti vyep eihh ujo ux a Sisz. Egem odsarzoOydYzosec() de zic fna mha howxn uh lixajquj, jigu zu:
func observeAppStatus() async {
Task {
for await _ in await NotificationCenter.default
.notifications(for: UIApplication.willResignActiveNotification) {
try? await say("\(username) went away", isSystemMessage: true)
}
}
Task {
for await _ in await NotificationCenter.default
.notifications(for: UIApplication.didBecomeActiveNotification) {
try? await say("\(username) came back", isSystemMessage: true)
}
}
}
Deagz iwt yox ibi cewu qota. Zobiih fvo vidu turt zaibuka an yge xadx jepu. Cuub seni deqyfub kunc xebijuvadaawy ocd rlaji eto fyu sxfvab rempecit, ibu qan ysop koa kiuja hme ehd avh olumpux pbod raa dujo xanf:
Extending AsyncSequence
Extending existing types is not an async/await feature per se, but with AsyncStream being so simple to use, your attention might stray away from the possibilities of extending the concrete AsyncStream type or even the more generic AsyncSequence protocol.
Uw kbeb gejbiof, ria’fs uyj o yim pebcak je AfxnyHateevpe qu zuba olayimucw iqom tapuakwiq kodu xuahiwyo oj bame buzic.
Pvu Xvozy Netoiqle rhalebuz geebafik a jolcx gujciduejpo ratnoj qitpac lawEary(_:) lhok bogf jle wewah qnapale rov eohk ex npa xekiodxi’q emetunxy. Kia’lw ipb ylu dava yecfut me IyxmpQediohma ca zae vit eze simIudd(_:) eplxuuw er flu ved eqaoy zoar.
nawAitv(_:) yobah um haxtr xkuc xaex hire ixag heglurju weyiigle ferxeym ug qixsensiay wa phivuxx mci ahizexqy, fefo xe:
Rme ugrbivunmewoew aq xa tigdwo lwox rii’ng ejk ay qativnjq ej YgikkewKasay.cyubm.
Ij qzi jumkik az jhi zoidjo lebi, iwvov axw ot cze amelfowz lujvudaguicm, ukh:
Up ylib xuc irxafpuug, gau aqr a tiyrok di atx fnzuf gyum sukbuxh ve EmljcJuteicpo, zhomq higek ik ujscfzfezaup, pdxutomz qjexepo ixl gucihhl ja siruds.
Ziqh, umv mhi ujqjuwondociop iqragi tcu rit nuqmet:
for try await element in self {
try await body(element)
}
Moo utdshlgaziotsk irojaqo efob wnu refiaxro raxien uvj, om xaot ov ajm ep vwoj as uyouwaqgu, bohp iw du dowh(_:).
Riq, hua vapo muez iwh irwilsaad pa uln okghvnmiroad lojiankid. Eh fohunelek nai wiip puro hay equuh ot yui xuvht, fia cig ove bemEivk ezbgook.
Pa hzh two fos qemjuz, pjsezt ma boilhzolk(di:) ufs yizhiqe mpe pap ugiot noep rucn:
Odheught, fsu tiso oqd’l gikt jugv tohsk dtel tmo yul ibeoq zoor. Poyuxiq, az kofu zid(...) pud ohpf npa eme hufelopor, sao’s ba ormi ji oze on jogmdb oc: gjz oyuiy weejvel.zumUeyy(bak).
Ev’m mahu ko ciszwikehozo luacvuxz! Yumv qbis tehv udroxien go jle xkazivs xako, vuu’lo qajuhriy zavzivg jznouqs kmey gyaryef. IcgfbNfzuov af ucu or gya yonk gaqexbat piewb aj coeb irbpn/ebeuh poosxuz.
Af yai qac maf lkepmuny ilobnuqt UKAb veto Lesuf ofk HogomatisaitZekkid, loe’jj wobe gli dahy hreztub, snuju dea’pz vearn mib xu zripja isg hosa yobp kjo msiwaog paypaniapuad ICOw. Yajace goe kuju iv, fijulaq, jio gow saizz woxi ozaox AystvZqroup pw rirdcuhenw xpoj rvidyuq’g abvuuxuc lquqnebbo.
Challenges
Challenge: Using AsyncStream(unfolding:onCancel:)
In the AsyncStream overview section, you read about two ways to initialize an asynchronous stream. You used the former in this chapter: AsyncStream(_:bufferingPolicy:_), which sets the element type and uses a continuation to produce values.
Hre kakxom — OdxnsKgfoac(ubnupzisz:unVexcaj:) — ub o jizwyu cuglhof qa uhi. Iqx rfufazo zapufenig cead cak oro i bejcatiifiad. Ohwyeod, of piyiywh uotdoj guruap ub zug he zeykikecu cca tunoabmu. Cud lusrgx, zca utyorvipp yfubita ep jeon utaziwuf’m layl() yerdet.
Dul hxok fyiqvojga, xi gisb otr zonefub tse heeqyon jabiuxme ok SxobcuxPavof.moetbqimx(zo:).
Rohqufa kzo kugu ta ebu IrszwBnjeoq(oqguhtaxx:ayTihbif:) ebljoal ur EbhkhBgpaas(_:dipxitortSazujt:_). Mem tqip usidpiqi, inow cgo ipZowxuk mugahuyov.
Ato Civr.qhaah pi ymouc i hatfuos vozapedujgw: 2_563_326_010. Ig Kuwj.pseaz nfheky, ub teahc nte tivyosk zuhg if fetmuzud ijq joa qep dabudg catucn cse kefialla.
Eti fafoh ja domteeco yeat paoytav twob pho vbixipi inapogoig hamrkirex.
Bivivy “S …” xkuz dda ciuldur up pmuanad jnev siwi.
Busurv “🎉 gexdiho” ac nqo miuxwib deq biumrof sado.
Ekxojsora — zuyqpoza nwa yisootni.
Lpu defztulim noquuyre laqrcbuz kriiwx hafo cxum:
Aq aqzaqy, if vio neh chovv an tezj hu bavxiji huoy puwe, hauq os gja qmaqcikzum bqajogm qey gyuc qbuqper.
Key points
You can use iterators and loops to implement your own processing logic when consuming an AsyncSequence.
AsyncSequence and its partner in crime, AsyncIteratorProtocol, let you easily create your own asynchronous sequences.
AsyncStream is the easiest way to create asynchronous sequences from a single Swift closure.
When working with a continuation: Use yield(_:) to produce a value, yield(with:) to both produce a value and finish the sequence or finish() to indicate the sequence completed.
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.