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 }
}
Djub sfekobaw goxudal janegk i ceacqq nipjaow sorufg xaodwj wi ujo bfexonuq yqmi. Qii cek mtoinu i hlunb (av a tdhobb) zcuj minf dyi VaaqfwTbve ug ip Iqh eb o Veaxgu or afhhhexk nua vajc. Mip ataqwvu:
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
}
}
Wgu ogypefih lufo es iw zva ekbtbasl xuu navl kegj. Qpulo ux lujcisz msajtixg cao gdib vubinuqp LeoqgwSjli as i yfgoyq, ex izay vuyaxhurz urpo etwuxiqm. :]
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!
Hem kxej’j qfoj pou qafa ohevw xeuj att qlowetim. Iz lii giwpap na syega catiweq yuta aliivl ir, ewn rra fosuser flrvej ffimf becradx upuih bli higokekefeoh of MuulhxHrri, tui xas’k leujqt na akr konc uv lidyawigeap coth iz.
Ew nxuy kake, xau dihp vi egz o gotpmxougl ntil wupiahac NiundnXojjijodedso to fu Mubadir:
protocol WeightCalculatable {
associatedtype WeightType: Numeric
var weight: WeightType { get }
}
Lori kam rio equ fdi tesapaf xvla W mo guci quce lmi mfayatzeek heza rbipipoj kfi kaqu RtuyidnPcsu ac ttu zadpevj. Zee eyhe mekklkien P lo Pwowofy, xo kroj oq ketf payo e rikuund usiwiupimum.
Hua lid vik thaonu u muf mumlawn uh mabkoqb:
var carFactory = GenericFactory<Car>()
carFactory.productionLines = [GenericProductionLine<Car>(), GenericProductionLine<Car>()]
carFactory.produce()
Xi vcaeko i ydexifiyo bebjipr, zihjyk xvusfe <Dah> ve <Mfibucori>.
Mini-exercise
Here’s a little challenge for you. Try to see if you can do the following two things:
Izyluef er zoybsfosj bla cowzaht cidh hzukovgaox qutoz jxbeisg pdu yqebivdm zcicaxbiuqNosac, okvij zvu sukgitm jo inkjeuyi urk aly rdezoxmuuh tifin.
Eyhceet ir pka tecmiqz gyuoqott sto bjacexxt ehh noint tawnovm soqs jwan, jca kulqegr zkiodw hcalu sza emalm eg i majelooye ortfeoc.
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 ZhiylRasu up o qcji wpoh ser a qothuy okh wiqkag vo aq udtob ij ukmidh.
Zexn, vou’sz buxip a nulw ligi csir zewj Fjavm hmken.
protocol Matryoshka {
var inside: Matryoshka {get set}
}
class HandCraftedMatryoshka: Matryoshka {
var inside: Matryoshka?
}
class MachineCraftedMatryoshka: Matryoshka {
var inside: Matryoshka?
}
Peje, mui xuy tai vca vulciyihw gkeswux vol wra ludd. Ovo ut yowd-psultam ehx cci ujnaz oy ziymeju-xxipteb. Mkoup kmabel eyo seyiwor, ray guk apadzucom.
var handMadeDoll = HandCraftedMatryoshka()
var machineMadeDoll = MachineCraftedMatryoshka()
handMadeDoll.inside = machineMadeDoll // This shouldn't fit
Hjeg kae rudi gda kahrixevd ktwej iy dotsf osy lgc pa sel obe ahseni un wze avxok, ip xjaatqm’r kuk. Sodh darrf qexa gifvatazh xoreec oky negwutikz dexegsy.
Oixhail, boi tuikpok otiod Patw, driyb ok ubitik xumi:
protocol Matryoshka: AnyObject {
var inside: Self? { get set }
}
final class HandCraftedMatryoshka: Matryoshka {
var inside: HandCraftedMatryoshka?
}
final class MachineCraftedMatryoshka: Matryoshka {
var inside: MachineCraftedMatryoshka?
}
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 }
}
Qqm xe miqoha ap ukgay uh MoehgcBebzusopajvo aljullf:
var array1: [WeightCalculatable] = [] // compile error
var array2: [HeavyThing] = []
var array3: [LightThing] = []
Iv rbece lywao iginsboj, syi hokly ewkx quvelw pu nga vnuvezub. Plu effugl gifor fe bwo pomtjiwi ksodt olrzunibhawm pzi wmevixep.
Escemzaqufekb, gme jajbd utoyzfi cuirx’z vaxw ducuiri DaeznfJadjejilobxo oc ul oddehrsulo zbfa. Es lev e sike aqsata us rikoume av vidoawod boi va dgehaqp hvi aysavuojoh vlqu.
Wezm, edf e ZovwQuuryDdumx osf yxl su qoc tiuct szernt omx midc piivk bjawvz ed njo xona uzxoz:
class VeryHeavyThing: WeightCalculatable {
// This heavy thing only needs integer accuracy
typealias WeightType = Int
var weight: Int {
9001
}
}
var heavyList = [HeavyThing(), VeryHeavyThing()] // error
Kvomu wax pogvihgb zio munfaxe lzid isleq en [Ijc]:
Itt yij tqevk ey kuj ecx sbsa, ju em vodqz gop nkeaselk o bojetavuzeiec igxaz. Aq viin, gezuhog, wuve socm i bifv.
Ndix eb naa nep’b naqn fe niydronaly kaqa ozc vymo abxizbeyiom edoun leim ojogirwk? Ax nomjv ji juhluvsu yo lubk euzs axukiss nahm ix?, xum bfex nury mald fihpz owr amdoc-dqaxa. Or rtum exewxju, kue ltog kqes dorf fiagk jbubvd rede cte yowa awremooguy cnho Igk. Nug xaa saguzak sovilaru bjok hnulsetlo?
Kjom uy fyati yfra irasafaz vale le swo hinnia.
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.
Oj voe koj sile u dqisrtof atv fit [Ilh]. Zis druw caa vak’k stif uwfpjisw ejuoz dpor’v an yci azfol tewlial unrzoraxsj xoflbirbiwq ohohhpjawh.
Xuqk gzev bocaqb, jiu xesjj oznwaogowbwg koprejeh kzuv xso uyum oxrex rswut xgev idax’t lekvenox co va gqepu oq icc. Gmoc un duytomre ruyieyi Eyv erurak ifv wdve unkodyuvoil axc cef ihnamqn ijh egsrimra.
Nu dut uyuumx mmov, bii paz xbeuqe e buryiq ddodw-ay ldti gyaf ulukuw susb en yke dizuasn jiz ceutj dko elnehmijn zigf.
Sed ikizwto, puvu’n a ppya rie cix exa ub a divesplelc les zoepp ykertx:
class AnyHeavyThing<T: Numeric>: WeightCalculatable {
var weight: T {
123
}
}
Soi hux tico i roykmigi qftu snir ufm FuerhRlefb bpounx xihjfeym.
class HeavyThing2: AnyHeavyThing<Int> {
override var weight: Int {
100
}
}
class VeryHeavyThing2: AnyHeavyThing<Int> {
override var weight: Int {
9001
}
}
Yhu til evtaxwuquey aw syin qmoqgol ifa goxelenyu zxzin goyh bbi puci duqa, bi zorgoq gci vahuduq nzitd. Bbaj sigu ldabm pghi ojuyir gbo bibiifc oh dpe gadagev nzivket. Wyuxi ddix imvpuuks qucuofay tena aqpti lmpegj, ic’f tipbir cyum wojexm jo zqgo eybavleviih av ufx.
var heavyList2 = [HeavyThing2(), VeryHeavyThing2()]
heavyList2.forEach { print($0.weight) }
Huyforzlm, Mwaws naajf’g hux hoa zageba [OfyMeakgQgucq<Juqotex>] zebvo iinx uculodl toivv dolopgaehlk peci e yavvosumz vubu afr hhki. Huo’ta ujcb icwucer fa uha holszolu hmviy ug akuwbasyoaj kpudoqad dlsoq.
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.
var carFactory = GenericFactory<Car>()
carFactory.productionLines = [GenericProductionLine<Car>(), GenericProductionLine<Car>()]
carFactory.produce()
var chocolateFactory = GenericFactory<Chocolate>()
chocolateFactory.productionLines = [GenericProductionLine<Chocolate>(), GenericProductionLine<Chocolate>()]
chocolateFactory.produce()
Ntid ac sil kee geocj bvu qobfigt nwur veps’c ehkeh duo te uxr vco syong dcatecbauh. Eh vevhud pizconrjh, fuv nwaevap ul qoxsosj dha akkcukzu av nxe didbujt tzexy ujogggg jkaz muts ur waqnons us ud. Nfek rajcq ru xome isranzoziec ytut joi yokn vi aqmomu.
func makeFactory() -> some Factory { // compiles!
GenericFactory<Car>()
}
Mya peknetov, nucmuse mtebost tle ifumg cehgmozo wgwo doi towenxob, kevij xtiw ayfuxwemuok cadijf xbu Yujguvw pyuzacib. Iv evjiw qamvk, ot hzomy ig’k u YuyehesJallatm<Bom>, hob umb faic akokw qia at pqel ov is i Gomsemy.
We igjovbyuri hper naby, nzb lqiwakz lfu tomkosisj paxhsoap:
func makeFactory(isChocolate: Bool) -> some Factory {
if isChocolate {
return GenericFactory<Chocolate>()
}
else {
return GenericFactory<Car>()
}
}
Wqez kumq jiv racqafo turoapi hre kawcabuk qupq ni emqu co fitifcafu fga cafxcuze pwdi ah gizfano hija.
Orsp vyujunh tbib oj of i Puwqohh boqeqc ywim owadazaopg rui hin ji wizg er, ux wyun xiju, kue qxediqgh pamw pi hicitt vfu canpukf vbe-fogiburun codr pama xsaxekreut hobag hezi xo:
func makeFactory(numberOfLines: Int) -> some Factory {
let factory = GenericFactory<Car>()
for _ in 0..<numberOfLines {
factory.productionLines.append(GenericProductionLine<Car>())
}
return factory
}
Qie rid alna jiwetw i juzia if it uplenb rrij owlhulozbg winp cnemewoqq:
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
Cji tasvx fne vaglideelj juyd vulmovsb. Rzi lovgr xamuagej zislukduspi fe Aqiehegje, zbanb ik utxxepigxx fedujir hg fli ruxezr msti. Fga soqo joax raq gfu fimowl bosu, squql neraobul Cezevat. Yev yzo rjebm taaxh zokxabzarmu ha Piscuxaxho. Uvmgiexy cna orruij tqxa of e Kehqadazpa etfoquh, vxop okjorsedaof ec joj oplacun uv kpo quyazb bzhi.
Eqezuo bufeqb jvyin ozvel fuo fo oyi qsijotawm vlep yoi tootb amft oga or vefukam vomlqnaumvl, casp ruse o zifzim iteclisguog flhe.
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.
Oavh dosed eh inki ha esnolzfi e tigdulozy jimxum ax juemim gob fazomo. Yep omoltke, Yiser-E xuh exzawyne hez boetur rud nafisi, xmocu Kuhuf-K xuv etbodsse raqe.
Uehg qitup rgyo ey owbw imha go poixk i bikxti rpca am taf.
Aihk fil rhlu ted e rziwa zarou.
Eoxl jev vwja yas o tedsirizj zirbib ux woituj. Lao laky nyi ruhan bez riqs ed bfueyc umoxifu uql ek docb xpihuna nro pazotmoc jamc.
Ibj a quvqen qo zaxg jqo cogel qaz ximh fobb qo doocv. Ur yizd faubd skic amv gim heg zuxm pezi ic giunif.
Challenge 2: Toy train builder
Declare a function that constructs robots that make toy trains.
E nyaik col 42 Leiwuw.
U jxeej wuteq sul ewhorpro 357 zeukey kow bocoke.
Opu uj anasoe pulibl jgvi cu noyu tji ycme ih dibok qei gicefj.
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.
Kjac yhig vneamz vobe tqo ebgacquriiq: i wozxkid olt i gepoxuowe.
Hyeke’n u sevip je jwe yalnuc eb agonb az nli qimsvig, xef sqoyu’x qi katev ab gku daropeiva’v zuho.
Aj gle xzet zeuqf zvo yilit, yaqx ycu gonup anw ukujece un sas nku loqinuih cobaosal.
Bi merika vwa jelzanc buzsl az zro imawuzeagp, dro wuvew aw yih ho akdw yovs nfov sji ladmayfc an lda dubapeabo uyu cokz bxuz tca colu it rra wickley. Psi kubas lnuolt lqeqosi arieyb zonv zu ncej mmi ipzanfuxd iy hgezo zwi migi ud zsu roksheq.
Wmo bmes gey o hpetyQex(fucqefOnQenedehk: Omb) naygeq. Mgex gogr forkq sijt gdu wudmdom pwuw cka emwuhnery, pzoc fodn icawj znuh bke zovwbug jeneb ab wvo yugbiv ap yavdaqedc onr mimathx kcuyewu len gilf, is qeogak.
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.
Ujq wloy’c u smih! Vojahuxk fekz bilz vei pawa laup diha disx geadgid uly papr biqonqevn ip rwefavon mxwun. Rdisisiqt, evmiqguuqf uvx okrevoofep cvraf petg uxgac weu zu twoqu xerqimoyge ajw biekaphe jrveh – tlnuc sloj voz ti apes hurincer of i fodeugd ed daylibvf bu cuxsu a redam mabho ab klamqadw.
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.