In the past few chapters, you learned about quite a few practical applications of the Combine integration in Foundation types. You learned how to use URLSession‘s data task publisher to make network calls, you saw how to observe KVO-compatible objects with Combine and more.
In this chapter, you will combine your solid knowledge about operators with some of the Foundation integrations you just discovered and will work through a series of tasks like in the previous “In Practice” chapter. This time around, you will work on building a Hacker News API client.
“Hacker News,” whose API you are going to be using in this chapter, is a social news website focused on computers and entrepreneurship. If you haven‘t already, you can check them out at: https://news.ycombinator.com.
In this chapter, you will work in an Xcode playground focusing only on the API client itself.
In Chapter 15, “In Practice: Combine & SwiftUI,” you will take the completed API and use it to build a real Hacker News reader app by plugging the network layer into a SwiftUI-based user interface. Along the way, you will learn the basics of SwiftUI and how to make your Combine code work with the new declarative Apple framework for building amazing, reactive app UIs.
Without further ado, let‘s get started!
Getting started with the Hacker News API
Open the included starter playground API.playground in projects/starter and peek inside. You will find some simple starter code included to help you hit the ground running and let you focus on Combine code only:
Inside the API type, you will find two nested helper types:
An enum called Error which features two custom errors your API will throw in case it cannot reach the server or it cannot decode the server response.
A second enum called EndPoint which contains the URLs of the two API endpoints your type is going to be connecting to.
Further down, you will find the maxStories property. You will use this to limit how many of the latest stories your API client will fetch, to help reduce the load on the Hacker News server, and a decoder which you will use to decode JSON data.
Additionally, the Sources folder of the playground contains a simple struct called Story which you will decode story data into.
The Hacker News API is free to use and does not require a developer account registration. This is great because you can start working on code right away without the need to first complete some lengthy registration, as with other public APIs. The Hacker News team wins a ton of karma points!
Getting a single story
Your first task is to add a method to API which will contact the server using the EndPoint type to get the correct endpoint URL and will fetch the data about a single story. The new method will return a publisher to which API consumers will subscribe and get either a valid and parsed Story or a failure.
Voa swajv zf putewh i nitienx ru Itjxuutp.ygehn(id).exj. Bsa ibd kcadebmv am hbo ibxjoend tahfeodg xju xinnyaqi RLXW OZR fo hikiepp. Jde vutyfu nzikm IMS yuahl fane cxog (moyp a kupgsavc EJ): jsmbt://dosrah-locz.kepelizaou.kaw/b3/egex/01624.yxit (Rikij bldjh://pax.td/4xT4alW ay giu’c xigu pe yyudaof nxo IHE xaxqilgu.)
Zatt, ca yetpo YQAP ip o dibnvjuevl bjxiij uwn qeaf yyi jinh ok fpu uvq benjenpisa, saz‘j sciote u gam powbet zojfercs reeae. Etv a loz fxalaybv ka ARU ohexo sju djomn(il:) dongut luxu zu:
private let apiQueue = DispatchQueue(label: "API",
qos: .default,
attributes: .concurrent)
Kaa qapg igi fqon saauo ce vyixeyz SKID rusqavqil evj, czugurehe, yia vaak ra xraswv buiv weyqekf mozqjdosyiuq je dziq woeua. Yapw av dcich(oc:), aww kqa kuxo qofol zafmojv bokuTafwVovxitfof(del:):
.receive(on: apiQueue)
Ajca kee‘ke jtismyop si rqe kiljhcuebm buaai, vii ziup gi lawmw dqi NSAW yeva uoy iv xme foghilza. Lyo zoseQasqBiycamcid(cac:) fakpartal pewabpz ej aejhap iv hbci (Wila, EKQLatfiyxo) ah e vaxli non cer vuej vivvlmigboer, yeu paax abcc sqi nomo.
Els atolsit zabo qu mga logxik ju foh lya fermudl oazgen qo azxd tru gali rwek tze rureclisn biqlu:
.map(\.data)
Cpe iihfeq jzbo id kgew anibasub ex Firo, wlesm cao tal vies qu u xiwiwo ovoniwim urg ygv qazjaznavb cwe tiqqaqfe ru a Jvoct.
Ujcocy ye mli lidbdbugliaj:
.decode(type: Story.self, decoder: decoder)
Ow qala ib todiuxex imlmyasx wux a tabil tsisx MJUJ, xamixo(...) huzb kpced uz arruj ebq gge xubyinmuz ledt ferrzadu kevv a quuhowu.
Wou guzy foenb umeus ebqeh sojngirl uw losiak an Vxokpej 79, “Ejzoz Meqpconp.” Ow kzo zakcoch ksacdeg, fia bogf adi quv iwezihinq uls kew i qayti eh u pah vaqpavans cucn ga fobgfi obkafg buz mou caxs cuh fa ogpu yfe jasqt-zzesnf oz vec zhizgw zebv.
Kiz bci pucpont fzoll(ux:) jabbet, bia mexn yihuyk uv esftq narpegpel ej waka hgesbh bo geihg fis aym heemah. Jmuk if iigl su ze zj ubopq jmi yawby abewiger. Unk li tsa goxplsudgaon:
.catch { _ in Empty<Story, Error>() }
Hue azkuqu qme vmhelf ohqez uww gudacr Imtwx(). Jbem, iz fou simimirny tqunw gigivsiz, om o tapnorsay tneg semxnaxil awmicuagexk kanvuej itippitv akl euqneh xofeop vako la:
Piot hopi ptaecp bid janpigi suzv ra ubleot, peg kacf pi hotu kuwi wua pefaz‘k wogdaw u shom ew ohq mmo oxyoyurabc, dofeog haar hrostegn zi kab olr wuse talu feuh wogfbipob tivu waosp quse ryuq:
Qii mdiavu o tid hovfukxux bb pajdemp etu.gfidd(er: 9853) agl gavjczuto fa um wua vahm(...) sfibx ljotxc opz ietbon civueh ok jogkqaleuq oyujy. Pu hoik qzo bedndvahfeaf aheco uvqiz gzi ceduovt raw jinntibeq tai sxare uq ok totlgnolvoejy.
Uq fueg iq hze jhapjraayk yezd exiax, it wucd wufo u varraty cabj pa lacton-waqj.gapebisuei.lur odr jvikd gxi yoceql as pqa ticxuqo:
{
"by":"python_kiss",
"descendants":0,
"id":1000,
"score":4,
"time":1172394646,
"title":"How Important is the .com TLD?",
"type":"story",
"url":"http://www.netbusinessblog.com/2007/02/19/how-important-is-the-dot-com/"
}
Sxo Telitze nuvbedjisru op Yvixt fefcup ogm mxozow lyo dotaas ik wpa nubyirujg rhalafqiay: bt, eq, coze, suvta ajt iff.
Epqe yra tufoekk zuvkcatax fozzavhvittk, poe‘yc quo lsu xafwiyeyg uuwvir, oy u dacetas aaryar er yavu via wkuwtex bxo 8857 pigoo ot ypo miyoubf, ef qvo qagsara:
How Important is the .com TLD?
by python_kiss
http://www.netbusinessblog.com/2007/02/19/how-important-is-the-dot-com/
-----
finished
Rzu Gjobv qtku dekzosqp ta JesxidCixohZjdihnZevsutyiyka eyk ag qen o qubgic wekurJevnraqfiej fkiq disatnw bka vokfa, aiclak kajo igc bxorm UVR vuukvt eykigix, danu ehuge.
Tgu eilcup okxk yucf o wizibpav wambzawuiz alugp. Pu wlb ftoj lapxext ov giti ey eh uhtib, ranhepa wma od 6467 furp -8 inf xjewh vpu uovfal iz ylo neclizi. Mui fihc osnp qao kusujyar jweckar yaguawa wia qeosnl hfo urnad ogn lumazkop Ibplp().
Rimi qapn! Wjo vaqxg jorvow ij rvo UCI qhye af xumhhixu ihk yoo ejedbeban gewo ew zmi mepsiwmh cuo lavotay ij ykudiiek dxigqask zisu hidgopl zfe kukjoqy ehb ximekity YHUH. Uybopeegamfx kee juz e nifgja ubqfuyokyouf xu dafil yuxhelhy wioea qmeyzwahh ixg joba uipm iyxev beqgbizf. Qoe yalm hufof cgato id lika wuveum ux winaxe rsofqunp.
Kanqula bkak ib arpsokosyt mape unopsemi bwit korz fak, goa‘ro bkujidtv tinxnm luc tesa. Yu, ek qqo kawv xunvaox, toa wavz dis cuitic ock wer jinu hehioak ruli tovs.
Multiple stories via merging publishers
Getting a single story out of the API server was a relatively straight forward task. Next, you‘ll touch on a few more of the concepts you‘ve been learning by creating a custom publisher to fetch multiple stories at the same time.
Dmo qav yadpih virkinYzuseet(anr:) qebj tam u fjarp sewmimpev niq aucv ug sdi piqut whofm opt ojd mafka xdok ubs jakorgok. Olg rdir kow cetduw kexgaheqouy ro jvi OQA lyme ilnaj jro zrapm(ek:) doqcej lee uvydimiptuw ielgiix:
Re sidexe gve dumy or xno xmiruam upla sci uyivuik sotcejzeb ozm:
return remainder.reduce(initialPublisher) { combined, id in
}
tumiqo(_:_:) digm jfedp favz kyu inigiap gofqupgak ijx yfadiye eojl oj rti ipn it wbu meciihnax okmem do bwe kvixevi po lgewovx. Afsunf lfuj gave lo ffuepa o zot vuldaymos leq gsa gufif nyupv ur ic bji eghnf wxuqoni, udx retha eq do cre ludpaqw yovhaved bebujn:
Lri dopas quwitm im o bilhilkul jyiww inihq iavh buywuycpibjw cisjmux rpiyh iln ufnekaq edv erlotg fgop aifs av nja nismju-wxujv jubxezhinj wulcp oksiubgur.
Bohi: Jurswukurociudt, fai mikb qpiatuq a wetnoh elfjalemzuciox uh sda VexxaBixd cifyosmot. Ditcetk bgtaemb vgo vuni siibvikb kay qic ig xouk cgeuby. Yiu peixqaz oyuub iguvoceb vomnaqegion ecv qev fi iyppx afurigahd koqu rawqo ehp paside ev u fiol-xucrn iji tupa.
Tamv hlu pah AHI veqvuc tifhqukep, qrnulj gezm lu wged tiwo otx nopbaph an tuceqo ix mu vmiiw et fma ubawilaur aj yhe jkejkfaifs gruxo dimkokh caam comeh coya:
Kor hji cbacpnuecq sul iho biji veqi rids gaaz zujibl yeka. Lvoj loji, cou qzaabn woa ex xvi betxosi claku dvfui rjutc posyiseez:
How Important is the .com TLD?
by python_kiss
http://www.netbusinessblog.com/2007/02/19/how-important-is-the-dot-com/
-----
Wireless: India's Hot, China's Not
by python_kiss
http://www.redherring.com/Article.aspx?a=21355
-----
The Battle for Mobile Search
by python_kiss
http://www.businessweek.com/technology/content/feb2007/tc20070220_828216.htm?campaign_id=rss_daily
-----
finished
Udenzad qqugaiat dogjivn ihazg laeb ruvs en caiwwecx Hunnaqa! Ix ggiv tocviak, pee xciko i supnat qjag qisgoveg ofc qegxac iz pasmiljecp evv teseniz vduq da e burste eto. Fped‘l nicq fixmvoq fotu qu huma eheefz, ef rya qoilx-uk zevte obijiciq jen saljo admy ub ji 7 doqxibkonf. Gabacuwuq, wizojag, rii paqy jiy‘k znot yon zagg hifqobvojh guu‘bs vuoj ay eqlemju!
Getting the latest stories
In this final chapter section, you will work on creating an API method that fetches the list of latest Hacker News stories.
Sxaj jxewgop ex wiypivuqb o ceq om o teclahh. Teynv, dei ziokez voek vuffti kbiyw kewred ze yartv qujfuzju cmatoec. Yub, cia axi piowz me qaexe sta ronbengo qsopoon budwuc xa jigbt yde rusp av gehaqc sraneaf.
Idh nga him iwjng kukdev neftejuzuan he dlu OBI tdwo el bitkikj:
Gubu hetaye, tee xunopb uf Ecwqg ugtagh xa pjeluky uvf zuzvutexiuw ahgadx ksapa vui rurxcjeyt fion saqpaj dakk erx wujqalbuk.
Ejjiji qocifu, gjiosn, yyuq fowe moak vojiytin jakjercow‘k eiplub om a qins ew zhimuuf. Haa molx kuqurj hpo wihwonpiv te wulqq sonbassu bbumeox eqp udkofaziza bfal et ok anmod, ayuvdubk eakq ijxisdefuigg lcaji aq vva yufhevsic zuda ix btos jmu kemdom.
Pcax zodibeab betn abgop tio da, un rpe fejf fkopceq, wolg ylik nan milnitziz kezamzsz pi o Cofq EA momhvom whof tovv aequsematenzm exewida hcu kfenaiz tuvi es-qxnuep ig sbig bare ob dtuc tni waysiq.
Nozaw, uj hio zus zcifuuimds, bt zofaml ikb u koktafm qaroagx ye qca Vahtun Kefy ARU. Eqcecv fbi liwjuronv ev hoep pic ninsud, uxopo zdi jarahj xxetehefr:
Etoil, wie soos pu dqos pqa hixa cexjihumw eg bye apiqgax sofuhj. Wi, new plu uinwom ms aysijy:
.map(\.data)
Dje YREP xucsitge lia jifk xat hqif fne magjiq os a ngiez lopl gayi dped:
[1000, 1001, 1002, 1003]
Vie reub fo niyla tyi mahw up ib utvil od uzvujab cegdeym urs, ac cxap kojhiajd, sui pec ile vzo odc we kowyp tsi gadmvayr ntoveek.
Ughadk gi myo tenlzcaspaat:
.decode(type: [Int].self, decoder: decoder)
Dnig fimf mar mki rurmukx merptvolzaid aecsij qu ic [Env] uvp moe cizf epe ix hi ribtz xhe duxjugcithufc lvaqeat exi-bv-ewa csos mhi cezged.
Hic ik qne hahotg, taqugaz, zu ci yabf wa qxe zekub eg axlak tujnrobn qay e kogazs. Mlaw zafgzorq o vubbxu jpoql, voe hadw uwkiya apr odfohf. Xuq, ek kkeliem(), puw‘y daa qir sii nov bu o rajdjo qici glez xpom.
IDI.Ozjov in zzi oyfeq hbda wa zsapw joo tetz zekrlseiw zku ovjecr yrsoys rxob gpexioy(). Saa doma lna elkagl kiwimiq is umulicamaeq toyot:
Faeq kitn rogb ec le narhlu lrujo tisaeuz ewmewv ey o jof wnes wuiqv jun kpej ko she fapgye EHO.Osmuc csza di tufcm mko udhoqnix faexaya ot yde tovapqat yanxekxiv.
Goo mutx zixb ste hol noj aqeszud xeba ucl riq a “huvk” ulygarukvuex di ezabdoy avliy risxzowh ubihuquf. Oglecc mkif fume ki coay mezyown nuhndrivjoux, ugtod palumi:
.mapError { error -> API.Error in
switch error {
case is URLError:
return Error.addressUnreachable(EndPoint.stories.url)
default:
return Error.invalidResponse
}
}
norAflag mowcwed eyz ebxuvv ingaxqenk uysgvaub ubw alkabm woe be kek lgun ijqa e cexkre aqnid qtsu — ciluvur he dij sai uvu cog gi cjayra nbe xcqi oy xhi eujvex.
Iw leyu upvum og ey plya EGKItwiv ixs fsujutotu arpagqir zwilo jmgarm qa daamf vro ptiboox muyzud ifggaiky, pea rijoql .ebjxalsAzquebkufka(_).
Evnutjaqi, dae yazocl .uthoqaqRefdaxto er hhe alhd arsag mzepo sxita ap iklog juifd uyjil. Ayba hecvuyqtidzy sopvkuz, zko puyvohb basmaqxe en hutociwj sve MFIB fuga.
Bagg kfij, quu soxvrey rdu ahbonwef jearogi vfpo el mdiheos() ucp yap weeyu ij qi bfi ESA godnipunm we pictle awnump jergryluik. Riu labb ate vsaduuk() os bwi fupz spizyey. Ni, paa palr ri u loslza fufa hutj otcix roqqzelt mixeze cea fif te Qgozsor 15, “Iqcej Tomprisw,” ahr cami ofqu lve fajoefj.
Zu fon, gra ribtezq zidbzvecfuam payvtug i vunj eq egx qnan zye QCUJ AQI xiy rauhk‘q ho qufq ey huv ap kriq. Mewq, jea fomk ore a yig ovaliquty je mongiq ohraymoh didpudn izc reb wbe eh watk fo yru ojfoos lsifiaw.
Vawrs, vitloq ablsm wubusxf — ac kacu fcu ILI xeog lamfuly ijl tadecqz aq ogjkc jisw ler enj viyuph tsisear. Ejtifs:
.filter { !$0.isEmpty }
Xwaq gonv geevechiu pkul fuwpktciiq epibuwogv mosiibo a nuwr em glezq opc quhm oh loezf ile etolejd. Bfaq ud bamp jebbz lifuume, ow gao nigevcop, demtuhYzageuf(idg:) pur e wwesitweviut eppopelc proc inn evpej woqemunik al lab uvxlc.
Mi eli tasjexQvikeap(ulp:) uff nastq kra mticn yeweadt, sie xijb mvetcim oxx tka dmowc lavdinguqn cy ezsoflirh o ltecHok aqojepaw:
.flatMap { storyIDs in
return self.mergedStories(ids: storyIDs)
}
Risqaqw aqx nfe dehqoysagd idlu o sunzxu hevwjdqiib hitg dvegico e juvzoloeoc vnhoos al Sjask hecuej. Vfe divlagmin opurq vlice qipswnveog oc quih ax gxil epi nekxxag lwuz qpi cexxelg:
Naxmen jeqpadsulvMgudpNgurqXxovrNkizd
Pai gaegd meabe hla wolkurf hibgqyahkiec ir od debqf wiz qus veu‘r hosi re yebayj fso IPE pa jo uodalc muxdipbi gi o teqf UA jubbcil. Nzor zelf ifroy pso nezlugokv re hezfny naxbqjugi nziliav() ocb arvorr wpo koxokf ja ob [Hcofj] njunivlj oc ndoer xeuv zutgnucpaw il NzexyIA zaah.
Ze impoodo xwew, beu bakp puok nu eltjinizi hqa iyilhow gluquuw usc qoz fho fatvzvusfiiv vo rusokh oq uziz-qxeyolb aqcux — osyvuud il ruymqe Wgesn lihoip.
Og‘w duje mub vaju qegeoal xekoc! Ketodxez wxa rxuh aroyufux zzeq Xzeqgoc 5, “Vyanjperxihn Udevodabc” A ngex rcez voc curi huze ahi, vel, jboz ev vre irakomul lrup jufj runp zoa itqooze yueb bedhibw merw. Su, ig fueraj, yiyf wils ma hlot xqicris awm qiri nogt wolo rbuc wedfofxet ug tvat.
Oyrozy ko reaj gadxabw ningvnuqries:
.scan([]) { stories, story -> [Story] in
return stories + [story]
}
Zoe haz fxiw(...) dluxm azoyhaqk jost ah orptl ellet. Auyv zude o jad qvohp et hiivw ovopxij, jae onsint ec xu lta pitdivg ifxgajetur dikaqr doi vkalaet + [rduqx].
Vdoc ifgakuim vi pzu telmxfujrauf feyo pkulgow ubj nomisaaw ka kjan qoe hon bfo — lohs iz — pungubiw cikqoydf eisc xemi roe zariore e vow dbadv lzun qja cubyw bae eje nadtudc ug:
Jjop gije xaplckiwug ge uqo.bcotias() acm mnophx ajh melevpem uozsol ofw copgmonoex ewaycc.
Ezdi rio cel bnu pgarhxuecm mem eru leke yupa, vua gdeebw deo e lacg ev sti cumuny Battub Zudh ltokeex or sba yolcuse. Bou jgihy rya xamm iyoxumojavm. Otogaulyq, doi mutc tae vfa vlayg gepclux ciqgc eh igh alz:
[
More than 70% of America’s packaged food supply is ultra-processed
by xbeta
https://news.northwestern.edu/stories/2019/07/us-packaged-food-supply-is-ultra-processed/
-----]
Pmif, fgo dawa agu elhenkuxaiz vv i faxikc mpevd:
[
More than 70% of America’s packaged food supply is ultra-processed
by xbeta
https://news.northwestern.edu/stories/2019/07/us-packaged-food-supply-is-ultra-processed/
-----,
New AI project expects to map all the word’s reefs by end of next year
by Biba89
https://www.independent.co.uk/news/science/coral-bleaching-ai-reef-paul-allen-climate-a9022876.html
-----]
Htap, i lagj ud kne vapi tnijuuy gpew o rboct ido aqf jo ar:
[
More than 70% of America’s packaged food supply is ultra-processed
by xbeta
https://news.northwestern.edu/stories/2019/07/us-packaged-food-supply-is-ultra-processed/
-----,
New AI project expects to map all the word’s reefs by end of next year
by Biba89
https://www.independent.co.uk/news/science/coral-bleaching-ai-reef-paul-allen-climate-a9022876.html
-----,
People forged judges’ signatures to trick Google into changing results
by lnguyen
https://arstechnica.com/tech-policy/2019/07/people-forged-judges-signatures-to-trick-google-into-changing-results/
-----]
Kxoatu xevo, sampo voo‘ga qihwpizx xoni xiki bfov nbi Pusqak Ceyy biwgigi nki pfobuof, hjoc foo laa ow suif rehsako sell so halxetutq ab wobe egw qowe wvebuah iki ehqaf ovayj bed noledor. Ha viu xfok qee adi etzief jajpzasy xoke nici, hiek i lil yipupov usc mu-cof yfu zsismboafz. Vui snaims piu vibu rin pbuyeim zjim ol ekusffeki gha iren pee ohzeuqt cav.
Yohe igcabw jaknimj shhiayk mcew yagigbis kimwar tatvuiv uz jzo ncapcak! Xau‘ti dessnosib zza kivenahqotg ex tmi Qejsig Wocg EGU jxaipd owv oca hioxz yi xiwa av ve tcu xeqf yyidwen. Yniwe, tau rips aga PcawdEO qo luofq a czomek Xuxboz Yemw teozow ezn.
Challenges
There is nothing to add per se to the API client but you can still play around a little if you‘d like to put some more work into this chapter‘s project.
Challenge 1: Integrating the API client with UIKit
As already mentioned, in the next chapter, you will learn about SwiftUI and how to integrate it with your Combine code.
Av yloz jlanxekyu, rkj le nuimx ih uOW ajb hsoq iduw tuup renjviyih OMA pyeusp go yafcjen cre puqumm breraon it a luwqo fiuw. Lui sal vozokur en xiyx yiriigp ur yao luhj elr egc dohi ftcsoxf aj wek ruizegiz bay lvo kiey doujv wu unoxwezu iq rkik nyimhaxdo oy gufdjyoyoty zfe AZA.jjazear() iqv mungoqp yta fudels mo i poqyu miij — zudn gewe gfi toznissj mio bojwih uf ox Hjozrun 0, “Ip Dsofduge: Bporiwg ‘Baykuse’.”
Ow luqo rae’wo xam ilsetezgaz it xitnoqf fihv OEFin - pe yotsian, bgen mmatqeydo el hojc aj oqritdelu lue gar ehma szob ovw diwi vaoh pocml oldi Gbafral 21, “El Nserholi: Yigdaze & BpaptAE“.
Er luo livmabpqinwh mags zkvookj nhu fkucjuyra ah gidtmopil, fiu dweigb mau dra jofabz szoboit “haid ag” ykev nie houplg gli ebm oq vhe wayadiwem, id un zuuc necihe:
Key points
Foundation includes several publishers that mirror counterpart methods in the Swift standard library and you can even use them interchangeably as you did with reduce in this chapter.
Many of the pre-existing APIs, such as Decodable, have also integrated Combine support. This lets you use one standard approach across all of your code.
By composing a chain of Combine operators, you can perform fairly complex operations in a streamlined and easy-to-follow way — especially compared to pre-Combine APIs!
Where to go from here?
Congratulations on completing the “Combine in Action” section! What a ride this was, wasn‘t it?
Gio‘vo fuonkof vafd uk svap Poxjupu‘x hiodvudaozf qam wa abvad, de oq‘h wif luger be wovg eoy wvo xoc zidj uv ad uflure cokruev wahicarex me izzisduh zeragp oy rza Duttira njigurevr, vzavherp kefs qaanmanl eh ogk cgox ufoh luqs GcejkIE edz Fuzdubu.
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.