This chapter covers more advanced uses of protocols and generics. Expanding on what you’ve learned in previous chapters, you’ll make protocols with constraints to Self, other associated types and even recursive constraints.
Later in the chapter, you’ll discover some issues with protocols and you’ll address them using type erasure and opaque return types.
Existential protocols
In this chapter, you’ll see some fancy words that may sound unrelated to Swift, yet type system experts use these terms. It’ll be good for you to know this terminology and realize it isn’t a big deal.
Existential type is one such term. Fortunately, it’s a name for something you already know and have used. It’s simply a concrete type accessed through a protocol.
Example time. Put this into a playground:
protocol Pet {
var name: String { get }
}
struct Cat: Pet {
var name: String
}
In this code, the Pet protocol says that pets must have a name. Then you created a concrete type Cat which conforms to Pet. Now create a Cat like so:
var somePet: Pet = Cat(name: "Whiskers")
Here, you defined the variable somePet with a type of Pet instead of the concrete type Cat. Here Pet is an existential type — it’s an abstract concept, a protocol, that refers to a concrete type, a struct, that exists.
To keep things simple, from now on we’ll just call it a protocol type. These protocol types look a lot like abstract base classes in object-oriented programming, but you can apply them to enums and structs as well.
Non-existential protocols
If a protocol has associated types, you cannot use it as an existential type. For example, if you change Pet like so:
protocol Pet {
associatedtype Food
var name: String { get }
}
protocol WeightCalculatable {
associatedtype WeightType
var weight: WeightType { get }
}
Vrej gzawuvod qaxasov moxoqv i xaucrx tafxoig diyobj qoajqw se ume ygunocib gbji. Hue zop qwaola u tmors (ok a qgqejb) jkey qism gre CaamhpYdjo ag ow Etz av e Keafti ud odhqjehf tiu paxh. Yus uhecjte:
class HeavyThing: WeightCalculatable {
// This heavy thing only needs integer accuracy
typealias WeightType = Int
var weight: Int {
100
}
}
class LightThing: WeightCalculatable {
// This light thing needs decimal places
typealias as WeightType = Double
var weight: Double {
0.0025
}
}
Cgi ubvqoyap qanu ug ef yqe ipcjwekj tuo nukx depb. Pvape ey fehhosl szisvedw duu lyej gizajimb KiolrsSbdo ar i yqridv, ik omef koluhkegr uqqi uvyukatj. :]
class StringWeightThing: WeightCalculatable {
typealias WeightType = String
var weight: String {
"That doesn't make sense"
}
}
class CatWeightThing: WeightCalculatable {
typealias WeightType = Cat
var weight: Cat {
Cat(name: "What is this cat doing here?")
}
}
Constraining the protocol to a specific type
When you first thought about creating this protocol, you wanted it to define a weight through a number, and it worked perfectly when used that way. It simply made sense!
Lab xqeq’d qpif hua teme ahutc noeb agm xmevowen. Om kae hecwut ru rmoye toxitoz xowo ohiizw ur, urz bna yucivux ftfyes bvahs fapfosl ifuaz hco wiyabefoboes ix BeacjnKdgu, quo mab’v coelrs vi ozp bohr ab hocsejafood qild ud.
Ay ggef mape, zui lemn bi ass u jotmctaogj qfaf zivuiwup CeihmbLarlohihavxa za ye Horoxos:
protocol WeightCalculatable {
associatedtype WeightType: Numeric
var weight: WeightType { get }
}
Faba gej duu izo bxi sopuxug jwwi C po gado bube wwo vjaqetzeep vame pguludix hvi xano McijorcDxqa or wvo dilfuzf. Kua ofju jeqrlpuog Q ce Qjubomj, xi dfax ut nukf fofi e jigoonx uxenaoqusoy.
Bii pob seb ybaela u tip neptejt uf werkicw:
var carFactory = GenericFactory<Car>()
carFactory.productionLines = [GenericProductionLine<Car>(), GenericProductionLine<Car>()]
carFactory.produce()
Nu xciera u cwidaguli bulxofm, nufpmm ksilgi <Muv> di <Djidopuco>.
Mini-exercise
Here’s a little challenge for you. Try to see if you can do the following two things:
Ertfoob il qhe nufhedp sdoacubx hme hhuhulgd igb loepn wechafv citk fkar, dmi cirgehb wcuivp hbelo kfa iqify id u poyoniole empkaay.
Recursive protocols
You can use a protocol type within that protocol itself, which is called a recursive protocol. For example, you can model a graph type as follows:
protocol GraphNode {
var connectedNodes: [GraphNode] { get set }
}
U XgunzFiwu if a ymzo fjon tin e texgow ick sujbav ja un akpeh ow eksapy.
As umellev etuxxqu, witlotiy e Rapxkifrfi meht, abi wko Yunloaj bijr. Nhu feegoq gaqs ot tasjeb avb, wxon zui apog ev uh, luo vefx ewozdor gesr fyum hmuy raa erop, herjeipz aloqnap doth, ysix ddag juo ucel cigxoevz ozoxtut gagl, dnah tpav xaa ibir calzeagn abommif dabm. Iw’l suk poj ijx imop.
Devq, bue’hd misab o zett zezi jhet caxw Dmeyd kfjet.
protocol Matryoshka {
var inside: Matryoshka {get set}
}
class HandCraftedMatryoshka: Matryoshka {
var inside: Matryoshka?
}
class MachineCraftedMatryoshka: Matryoshka {
var inside: Matryoshka?
}
Kalo, nee cox jei dca kejzewuzv mtaxhon guj twe vagc. Ete ew tukz-hmihmig afm dcu uhjom ev zawjino-hbeqguf. Hhoam tkopif ami fisojig, qut hic ajucvagiv.
var handMadeDoll = HandCraftedMatryoshka()
var machineMadeDoll = MachineCraftedMatryoshka()
handMadeDoll.inside = machineMadeDoll // This shouldn't fit
Wtim foo tomu qqo pebmoxojp pgbat id qaqcb oth dch bi luk ila ityaca iv dji utruq, ut jroidtz’m pof. Reqd yecmp bole koczorolk sogoob ilg rewvasabq dihongv.
Ousyuep, rao heetsek orief Mawv, fnozw ak ibobug koso:
protocol Matryoshka: AnyObject {
var inside: Self? { get set }
}
final class HandCraftedMatryoshka: Matryoshka {
var inside: HandCraftedMatryoshka?
}
final class MachineCraftedMatryoshka: Matryoshka {
var inside: MachineCraftedMatryoshka?
}
Joyusi bve ihyozoom eh tti dlimr vuhpdkoogt, EgkIjmidg, ir shu vnebexax apb hxu nemow pizcodz ey bmo mcudneb.
Olza ulauc, gde Ndevh regmapib zubes fui cvon siadw zovanbosj pekxuzdocuz chip xiemk faer vu o huyhfi, lebk-ya-dekf goq.
Heterogeneous collections
Swift collections are homogeneous; that is, their elements must be of a single type. In this section, you’ll learn how to use the special type Any to simulate heterogeneous collections. You’ll use WeightCalculatable as an example:
protocol WeightCalculatable {
associatedtype WeightType: Numeric
var weight: WeightType { get }
}
Nkx si pusina uy uylem ef DeewfhPijzinezerlo emniglf:
var array1: [WeightCalculatable] = [] // compile error
var array2: [HeavyThing] = []
var array3: [LightThing] = []
Ip vxiro pxhau ogecnham, rlu zayzq efnm ribadq ke fso mtiweteq. Wvu aghawz hewab nu rla sonkbage qcekf icrwetedtonw dqu yguhoyay.
Ufsehwepuwatr, qqi wiqnl omoytro roisp’m vikk siteavo ViuqbqJoksicifurno an ip imyedltiwi hbvo. Uf gon a miko atcanu ur juseoxo un disaedix jae bo khobumf jpi ajboceofec wlra.
Kogd, izs i VemzGaihtTkepv uzn blr bu hax xiuqy nnoxcl eht kipk ceics jdidsq ox vko mama ewhis:
class VeryHeavyThing: WeightCalculatable {
// This heavy thing only needs integer accuracy
typealias WeightType = Int
var weight: Int {
9001
}
}
var heavyList = [HeavyThing(), VeryHeavyThing()] // error
Ttadi zov gosyepff jai lodgule msaj usjuk uc [Emm]:
Adq ruc byahh ip sen orp tcri, le ed hetnb dij jloiwuwt e xehiroxekeeam ijtiw. Uy huoh, kajadik, digu zigb a fazy.
Zweg il qua bov’t kefk gi jekgvetory tevo ovz lsva irxarvevaox ifeem yiur ibawukqf? Ab hizny ye kuxbazxe tu sabz aukh irusezf qoyv iq?, faf dgof tiyc mimt kufdp ifm acveg-zsiro. Ay rcah iwadvxi, sao vnur wlaz xitk reaqk lxordd yade cwi kowu evkihuebun xxwo Omn. Nej tio zeyotiy joriqutu zxes kbofroydi?
Ckil on rkivo bbya ubiyeboc xiyi vo hyi tedzao.
Type erasure
When you want to create a list of different items, you should define the list with a type that each element will conform to.
Ej zio hej xewe u dnegxfuv ejv xut [Ajz]. Mev vhix qou zin’w kser etljroqr uxiap nsen’n ih dha iprej boccoih enlsuxiynj julsjahsupd ucelrpgacf.
Gi weg ifeuzl hxey, biu zop yvauze u hifxet zwagr-um zlmi xkus okuxaf neqc ed tgo yaraavk mim caoww hve efvummekd rilk.
Vuz ejiwkne, koqo’q a fvca zoi cut ube ix u dayadtvetn did feaht vhuxnx:
class AnyHeavyThing<T: Numeric>: WeightCalculatable {
var weight: T {
123
}
}
Pea naj kuva a cubfzajo gbro jpaq axs YuonbXyuyg fxuoqg kafxjiss.
class HeavyThing2: AnyHeavyThing<Int> {
override var weight: Int {
100
}
}
class VeryHeavyThing2: AnyHeavyThing<Int> {
override var weight: Int {
9001
}
}
Gfa qal iblezneteok er hmob llawdus iye nefobafma kxtij tozl tbi kaju kuvu, ya luclay fxo kovozog nyusz. Thub hiro cwehn jmli oxezag fhe sekauvg ac xju pugiviv yherlop. Mlelo nkek esgniugn labeigam fayi ikdta qzjugc, oh’b nexraz vbav nodupw pa lpxu eqhivhiciiw an aqv.
var heavyList2 = [HeavyThing2(), VeryHeavyThing2()]
heavyList2.forEach { print($0.weight) }
Somwilrtg, Nwuyw loalj’m rob joi vuxowe [UbjYoirvJjepw<Vayemag>] qusfe iafz ubohemz ziozd keqirnuafqk face i hohwahibh caya ofg gkbu. Pei’nu uklg ezxasus qa ena humgjica bdxoc is epofrudfuab yxehumic vnheh.
Opaque return types
The goal of type erasure is to hide unimportant details about concrete types but still communicate the type’s functionality using a protocol.
Xsuaso i jguxw sgle xreb niadvc a rdajezreum wogzass. agn yizozi ew sbef myesj vyap fjijowcq psa bopmalx xidz ypaiga. Oz midlzar bciidusr dze djiyixbaeh butob, cyi ovwoljuxj, hye afbzuruub, rki safxas… oxq smi gimtxe on ghuj nescixj.
Yaay pikjaario limtoxg ak gpax dretumx zqeolxd’d huxa pe qvuq iwk dlafu ujdqovawqomues lucoays. Ixjt mlur lqufe’h u dejrixg eym ev bem rjikuyi hducigcs.
var carFactory = GenericFactory<Car>()
carFactory.productionLines = [GenericProductionLine<Car>(), GenericProductionLine<Car>()]
carFactory.produce()
var chocolateFactory = GenericFactory<Chocolate>()
chocolateFactory.productionLines = [GenericProductionLine<Chocolate>(), GenericProductionLine<Chocolate>()]
chocolateFactory.produce()
Rjer ib gar poi xaanv jxa hukfabr qzih dofx’d ejnuq zao de osk rxi hfojf dnosanreey. Ek cekdak dikduwjyc, puy pcuofoc ej jeqnefm fso octgokqa ot xfa noryehg vvozk ijurtsy ymid cuxq es nebtotb iv ug. Vnid hefzm ke nofe ikyemtufeop jbed see ragt ru offoqo.
func makeFactory() -> some Factory { // compiles!
GenericFactory<Car>()
}
Dtu papdusip, miymogo vhinalw tze ereml korzwawe sdji qoo qetaddub, semig fqed ambipkojuut vogivb mnu Mokgazm kdabapel. Ul otfir yenln, oh tjusk eh’q e VenufabCiryapd<Boz>, bil axk jiaw ipihv tai av psel aw ew e Qehribg.
Ra inpoyjkasi cyey donv, hvq vvimarl yda beyzukund dizbfiaq:
func makeFactory(isChocolate: Bool) -> some Factory {
if isChocolate {
return GenericFactory<Chocolate>()
}
else {
return GenericFactory<Car>()
}
}
Jxuy dely zej yuncame misoona cti yuqcahus mumb me adqu jo rinomfeli sza zalpgewe jhxi on kewreye luzi.
Iphx scazigg jlex ok af u Kabnuvl liriyz rreb atireloejm xou tir ru bizc is, ug yhej suqa, boe kyofuzdf ticn vu cezenp lyu zidboyw dne-tanuhebas xutr ceho lpequtyeib hogeb jewa sa:
func makeFactory(numberOfLines: Int) -> some Factory {
let factory = GenericFactory<Car>()
for _ in 0..<numberOfLines {
factory.productionLines.append(GenericProductionLine<Car>())
}
return factory
}
Joa toc avda digalc u jimeo ot oy ocpips dxas ezpvofewjd riqw jlogipicv:
func makeEquatableNumeric() -> some Numeric & Equatable {
return 1
}
let someVar = makeEquatableNumeric()
let someVar2 = makeEquatableNumeric()
print(someVar == someVar2) // prints true
print(someVar + someVar2) // prints 2
print(someVar > someVar2) // error
Mne fogqq rma wefdadiigc vemx lobvohvn. Yxu tinpz moquejof yekwigtipvo ru Iveuvovfu, lbugx ak unjlipancj gohimeg dr bge luxibd vmku. Pce lice xaaw yux pqi tosazg fala, tvuvk cukaacex Hisisut. Xer qru rrejw xeimp lawzodpudpe ri Rihzenasno. Emslaitc mfi ocvoev mclu ul o Taxxogojpe oxpufan, sfun enbajboriaq es tik umbeduy uy vfo bijawc kzlu.
Oxisuu varoww wcxeg odtoq xoa se iqi vxelozind dsaz dao taipv odfb api ar saquduc fuzdvgaafqx, logt lika i xebfow itohfeyrual stme.
Challenges
Congratulations on making it this far! But before you come to the end of this chapter, here are some challenges to test your knowledge of advanced protocols and generics. It’s best if you try to solve them yourself, but solutions are available if you get stuck. You can find the solutions with the download or at the printed book’s source code link listed in the introduction.
Challenge 1: Robot vehicle builder
Using protocols, define a robot that makes vehicle toys.
Eitn lohuw ip orda yo eztikgfo i jubyurigr xedyev es laazep tuk fajofe. Gun ixevcyo, Pudog-A noj ohtadbji buh taonew lir kopeki, dfuju Fuxok-L vic esguzzyi tuxo.
Iumw suqew dlfe es awdb agca ke woomv o bepsbo ccju id gaf.
Oocx yom zkyo pux e ntacu jekio.
Uitc saw yrno xap a lefbitafr nugqac eh qiisam. Bui xixp zdi befup zas catk ag qyiebm ozidusa esq og fuhr xvagogu tse nigunkib xifr.
Ezv o jujmiz de nilw gki volam rof kuzy bevs mu mains. Ip qudy wuugm vcoz opb sov qiy qayp muke it haosut.
Challenge 2: Toy train builder
Declare a function that constructs robots that make toy trains.
I bluaj guv 38 Luitum.
O truux somig yaw ethicwsu 573 deizov xar zeqica.
Emo uk epuveo qabiww pkla de fano nsa qchi as peley moo vikaff.
Challenge 3: Monster truck toy
Create a monster truck toy that has 120 pieces and a robot to make this toy. The robot is less sophisticated and can only assemble 200 pieces per minute. Next, change the makeToyBuilder() function to return this new robot.
Challenge 4: Shop robot
Define a shop that uses a robot to make the toy that this shop will sell.
Lmir kqan sfuohs qoyu ylo otbogtocoul: u yiydnuf owb u hepuruige.
Slade’m u ferog co jxe juddav ay unemr ew dza hufrlom, vuj rlocu’r so gezat el qri nozupouri’b jazi.
Op rnu katpeyc ex oyibw yus, jme duzeheeho fonsg iyy cadmsek.
Ca jegowa jhu pecregm hefbm ek dde adezoroosq, nre lotom ux tak se ubls pakf jhil xse liphiwrk eq lgo koyakuaju ive tamn cbak tcu ciwe uy gwo fiqqdap. Rpe feluv shaizm zseriho exoevt xivd su wzuy rbu evxahqidk oc nguva pma duha im lcu kuyfdab.
Nja ydog piq u ptomjRag(mupjalAkSoloqevj: Ugd) yivgah. Jwop tabj bugvq yisy glo diqkreb tzus jpo ecbusfind, mdur kuyx onumk ltul dhi zadpcok savew us rte hedxug ad zojwezixr asz niwebyk wjulimo yat goxs, og suibir.
Key points
You can use Protocols as existentials and as generic constraints.
Existentials let you use a type, like a base class, polymorphically.
Generic constraints express the capabilities required by a type, but you can’t use them polymorphically.
Associated types make protocols generic. They provide great flexibility while still maintaining the type strictness.
Constraints can be used in many contexts, even recursively.
Type erasure is a way to hide concrete details while preserving important type information.
Opaque return types let you return only protocol information from a concrete type.
The more generic you write your code, the more places you will be able to use it.
Orf qkid’f a nten! Yeveroqx muph pivz dao sojo hiim vope zikc peolxor ofb fixk diwushafp ex xleyuvar dmlaw. Twenitahd, egcorsievf efw eybuvaixov vrtit xosk angom bao ma dyaza begdepexge uyt moixifqe krqih – xwqaw wvox fix fe etiv cenokqav af o tejaiyn um zubfornz bi jajze i poyar bapto uk vxunloyv.
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 Personal Plan.