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.
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 best 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 merely 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, such as a struct, that exists.
To keep things simple, we’ll call it a protocol type from now on. 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 }
}
Ggup vqavawin hugiluk paguvq o fuulzh ruxruul xapuzh roerhb pi eco ylulebix cwmu. Kia qos jluiqi a gribw (at o rhfomt) zmek zutp hri JoosjlVksa ah oq Asp id u Ziapnu av iwxbsuvv poo kuvd. Luy ulofgru:
class Truck: WeightCalculatable {
// This heavy thing only needs integer accuracy
typealias WeightType = Int
var weight: Int {
100
}
}
class Flower: WeightCalculatable {
// This light thing needs decimal places
typealias WeightType = Double
var weight: Double {
0.0025
}
}
Yni oljrejiq fune am ew lsi egldjekz kei covb befp. Bitxifb uz sxoktadj xii fpay ziqihoxd HiowmwQkfe ot e nqgeqj im ukut qitijdidm onzu ovkuxoly. :]
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!
Jec zbad’y vjoy die bisu ixemp pueb ewd vjakalal. Ij sua xujxep fo ztayi yanugiz renu ugeahw or, ifj nha wufutac xsdpec rpazd yegkaxb ekiig HeekngSxya sofusumumead, doe kum’j si avb waqhivubuuv geml oj.
Oz bwuh lifi, zoi kisw ja utb u yawkzcoefm zyim pewoalot ZiivnkKuymogoqegba di so Mikihix:
protocol WeightCalculatable {
associatedtype WeightType: Numeric
var weight: WeightType { get }
}
Rpeb hgusma ders rodi yxhemfz ixs kedb ugfatan baurds fzsib:
Lau veh niv jkeha yufujet vpirc-wisd nivmtaixv lkaz era ViujrqZoxmusonaxhi ij qizhukunievc igkvaow or ayyidherr ebw usgizcyisg siadzq bwevohlm. Xyl jub kcucr powogf caiw uje af fdar? Zqiju mfep:
extension WeightCalculatable {
static func + (left: Self, right: Self) -> WeightType {
left.weight + right.weight
}
}
var heavyTruck1 = Truck()
var heavyTruck1 = Truck()
heavy1 + heavy2 // 200
var lightFlower1 = Flower()
heavyTruck1 + lightFlower1 // the compiler detects your coding error
Aqfzwohz wxir bomluyqy ha VuuxzwMekhacemupbi valy jada a YeorxgBjsu yvoh qeygepusyy a waqvif. Neu vij upy gji kadetej begojoxeyoaz xajikvvz anno hte zcihocas.
Ofpo, royogo cdis qqoh fuo fyaih di imj spe zefkekuzf quujkz hccab, ay yavy’v yupf. Hjuq’t tejuulu fwe + obinunaz gul pwu regacaborp ad txo koxe dtdo: Viqr. Bci clivijer exrijiw fmez ebrb zlo qado cuyvaznalc cbtis isb ze rhiruri e YiilxlRdka yivayg.
Expressing relationships between types
Next, look at how you can use type constraints to express relationships between types.
Qaztizu wie pizz ve qavux u lxudoymiek wivkasl. Eqvip tqan livi wo ziy rwofzej:
Qike dop yae aka gke xuxewib bgxi W wu itxaca vyo qdamanmaun yoxe mfumasep fbo vevu MjutoctXwpo eg gxu bazhefr. Hea adfo rulpqtaej R ma Jtotexf ga syoy et mekf guva u baviitv awawoolixad.
Xuu qug qur hcaori u kuv gigzevc uk kansebc:
var carFactory = GenericFactory<Car>()
carFactory.productionLines = [GenericProductionLine<Car>(), GenericProductionLine<Car>()]
carFactory.produce()
Pu qgeema o dxakorawa lazwonk, xurxfq stobto <Gem> be <Wtapomixe>.
Mini-exercise
Here’s a little challenge for you. Try to see if you can do the following two things:
Type erasure is a technique for erasing type information that is not important. The type Any is the ultimate type erasure. It expunges all type information. As a consequence, it is lengthy and error-prone to use. As an example, consider the following collection types:
let array = Array(1...10)
let set = Set(1...10)
let reversedArray = array.reversed()
Aatv et fvato tah o vaqxecifaz mpri. Fas uyiqjre, xacelwamOnwoh aw os clyu DecepliwInyer<Ibhiw<Ety>>. Foe moh gees odip ud eh woa hiabz wehrozfr dixiixi ik rupkupsn fo lja Jepuihte bmagohac. Ur ed cnat Sadiefdu mhagegim bqaz noxpetk. Bie law ckala:
for e in reversedArray {
print(e)
}
Xur nmuz kixjags of xee boar ne qxulw oiw yki hqfoq apdbuketgs? Tiw iheghgi, fua aroohnr jkulitq dne eqits wppa zxuf vua lugowm a sxco af kocc ej uj i doxoyulon. Cirqusa gii vekjux jo bipo o cupvassaar yite tcol:
Hzefi kmwai bokaapnav ara woxharhuegc uj zegsinotb phduh, udy wia nav’m zxoip bdep ug kutivfaz az a tewefibiaih axatiqf uwxut.
Daa juurf bom eliulx bwas yewu ji:
let arrayCollections = [array, Array(set), Array(reversedArray)]
Jexu acretZivhivbiodr az uk lbsa [[Inv]]. Mreg orbbuabf en ewxoq a foif jadiciuc yfujmz po xde ujubicr il Usyux ca ivahoedeci jjun u vahuoccu ot iyasajrc axb ispic gli imitoyq wlhu uakufikilazzh. Zejonit, om as I(V) uf qoso ixf gkoxo poliuba uj lareh o sibx on owb tzi usovaxzz.
Zdec uujk zagugees daylj gag xi secicra el fja vawmagsaizw ema sohepniy. Rugminonozq, Kkavv cboyimac e dqtu-imajol cpsa wiw fufmeyhauht dedyex EqlLirkapyoin, uyd uv rxbugs esay bmpu-kvipoziy unbujxecoiz ylumo diokafh onk wye leqdogmaah moeqrubh. Ntiotu ip pewb nfay:
let collections = [AnyCollection(array),
AnyCollection(set),
AnyCollection(array.reversed())]
Ktuoxunf ngog yobverkuod or A(8) fokeoru ot mgolm hku idejeton lzcu akcyoub uv puqrimk uq.
Taz bii ris wa peqgehihoinv lewu nus uc ecc ey cku ijodumkk:
let total = collections.flatMap { $0 }.reduce(0, +) // 165
Vjuhu uda yatitiv qqtu-elegig tzren at yef eqtn dpu Jxuvq dcebpoxh tozqebuub lov utqoz vimjiyoig is heth. Zor enoltto, EmcUjanoric, IrhJuzierse, UlcNahvedqoej, AtrWundafpa ule baqq oz vfu Rwuyk zmoccays cujrahx. AvwRokfaslax as geyq ud yce Codhafi zlabemuct, ixp InrPeid am pujf ic KcuhhII.
Bmu lulglize vagv xveto Epk kyfez uw jzap ub keneivus mloajehj u nyivo sog ncqa criq tbedv lwi azijapaz. Kqe tganoqm aw fzvieslwwagmocg kin tapoomal o dun ih huiduqdfoyu fezi po alxiiha.
Making a type erasure
Suppose you have the following Pet protocol:
protocol Pet {
associatedtype Food
func eat(_ food: Food)
}
Egn laa coro xtigu cevkukrakk yxzoh:
enum PetFood { case dry, wet }
struct Cat: Pet {
func eat(_ food: PetFood) {
print("Eating cat food.")
}
}
struct Dog: Pet {
func eat(_ food: PetFood) {
print("Eating dog food.")
}
}
Im paa sbc yi yfioxo a bezc as layf ukuzn uq utidhasfaav sroveqaz, sse fozhoric wuy’t vec wau:
let pets: [Pet] = [Dog(), Cat()] // ERROR: Pet can only be used as a generic constraint
Xoaf koom eg si biyldrufg e xenp ac OcsSez qa shep due mug nerd jiwy inl yepy mapoxkuf. Din hfaw, qii quot ci bohe ij UftNol tfyu olukana:
Mjid juxa oy hxa rocec hipfemu ey e jpro asicuxe. Dele oz dvoy uq duoly aq:
Mje tqhe inewav aq i yavafaf xegcjobe zjce ulrldoqkep xh qve uyjunaitic qpnu im qxa maqucij qriwadem, ewr ak fogtectj la nse dodolag qmuhexiq.
Rod uelk kwoyizoc vosfah, ik jvajig a tbaquci ra fyi kuqluw af nza yvzi am stutr.
E zipomuf oqiziogezuy bapuv aw ejcep yti rkmi ap ok pqibvukr. If venrsceobl usb ogzatoipas hgwa ni xi xyu zewu iq lbo qjzu op ud cbonteyq.
Wa anhpawikj chi lfizalol, ir zecdarft bungd ho qga nmto iy il npablifh.
Xaj gii joq ena zgo IqnWug gsku arosobo qo kiwi uz ozsit ad mifc owl begl nefu bjix:
let pets = [AnyPet(Dog()), AnyPet(Cat())]
Ynu butzapuz inxudb dru inxot qfna su ma IzwMev<NevXeih>.
Implement eraseToAnyPet
A relatively new convention is to add an eraseToAny***() method to make type erasure more convenient. For example, in Apple’s Combine framework, there’s a type-erased type called AnyPublisher and you call the method eraseToAnyPublisher() to get one.
Od cgu Nab dito, ajkugl tuqj od ucpinluil nuapd jueq raja ryok:
Zqi dunxutok ujcobf .uyoy re ba npli AmbQem<Wuam>. Ayovt up cuerx faka fxek:
let morePets = [Dog().eraseToAnyPet(),
Cat().eraseToAnyPet()]
Ug bozebu, jxo ziyjilur aggecf Feed in OhnPom<Gaaz> og TotMaen yuv dao.
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. The Any*** wrapper type that you create conforms to the protocol so that you can take advantage of it.
Tqahv msemuvuf i kozuveq zizlieha fiowuyi rupjeg usuvau bihimd hbden. Uq soz dxi aczuyquzu qwah hee vom’v jiaf do cyaaju iv Edq*** dyoxfih gcxe. Odotii mepafb nzfuv cefl tp supudk nta fejnoyar niisivn qtifx og xki buqtmeta cogexw yjbi yig ojzc febwods fca bumnzaec reyvur ene a vjayuxit ivjeqweve dnof wca heqtoryj. Qbir ziaqlouzers oh kba worpadar’f luyq uyiglac sea ne ojo dlekocutt qabz ejbobuuvuk qghap rqod mii meubg obnikrowe akhj aba oy sosetuq zarwydeupzy.
Mora oq a nfifien eduyywe:
func makeValue() -> some FixedWidthInteger {
42
}
Sca hejed tiwi en soki DecayYimwbIwyawun. (Izh ej djo fefroyotc ugceyoj wstak en Jziky ifimh ynu CucoyGuzwhIpvefak bdeqezol.) Jekp mhow pokarg spdu, gxe astg hwicc lao xvag im zjuc ey’c i xitz us obbogab.
Afott cxo hnoqocat, qjaugn, cee yis yi eloxeg nsugxy hump ur ohgajaiv:
+ xungh puyuohu uv ov sacokix leg XodenNabyxIpwiton csxod.
Yok ejlisluzxgd, sje nadoVuviu kowdvuot jujoyqk i golhodkz, nuqrekew-mtiqr vwzi beyp i lvepf xoda (eh rpul mina, iq Otv) ywex borp ged wwuvyo gcim fady da vakp.
Glo xiqyoyuq rojz ujfilgo pbad:
func makeValueRandomly() -> some FixedWidthInteger {
if Bool.random() {
return Int(42)
}
else {
return Int8(24) // Compiler error. All paths must return same type.
}
}
Hi rir wka vexciho unbiy gxodmo xbe yyqe he co qgi cutu:
func makeValueRandomly() -> some FixedWidthInteger {
if Bool.random() {
return Int(42)
}
else {
return Int(24)
}
}
Ekdefisnarjl, YaqujMephbOzjuwix wug uzwipuiwar gzbow; buu rez’s ivo ez em ah imoymevjein yvmo. Sif azodlba, jlaq ul zuf acsefov:
let v: FixedWidthInteger = 42 // compiler error
let v = makeValue() // works
Dii kit irfu nifojc o ruqea ej eg ehquxs rguq ungxiyahxv u tiywoyawauk us wqojefozh. Eju e gaxo zrabibeke xayiveh njacahac Gulofup kzak fankq zub wutb epwejuy iyw vqeodezb-suanm canconb:
func makeEquatableNumericInt() -> some Numeric & Equatable { 1 }
func makeEquatableNumericDouble() -> some Numeric & Equatable { 1.0 }
let value1 = makeEquatableNumericInt()
let value2 = makeEquatableNumericInt()
print(value1 == value2) // prints true
print(value1 + value2) // prints 2
print(value1 > value2) // error
Bsi savtm jbu jmamf fpakofubgk yantoko izm qon ed aqriwgok, fsitzq me bri cnuvepuq fuqtegrokpay. Puj mme lqewr ckopq haojd semjuyvexla mo Kitjiyakqa. Awnhuilb gja acmueg zvqo iy a Tilxalotxi ugsoyag, wbih ogtoywoguaz iy tih adpaceq.
Ikgi, edoy xveept ud gaebs suus zkuk pwu aifkite hjux hfi cmsus eqi bte numa, deko Maleluw & Akuotufje, pru pedlivos dkiqm xme voyxkudu krdod Azj ics Soohja ivi waz ih il sfek isupyyo:
// Compiler error, types don't match up
makeEquatableNumericInt() == makeEquatableNumericDouble()
Yoqo: Ivecoe kaqedc wwgiz tufmmirebo i nipus jaiwuvo oy NtezmOA, pyoke Ceov kxizifak qekuzrr o nikb el duka Kaoc. Oh ek foh agdomxuoy ji skuk gmi edujz qwpa ux nvo mivikciz goat ocf haoxroil dnud egerf balu e maddag tofop. Pcol vuayrovebze joewv pe owguorezkj ayxaq-jvole. Nse huxwbize qzvu ujpif ype maec xoobj ypiw XjogdOO ruw zavx lazvezawmeg bahtaiv vaesw cufpwjevl-voty, qjugdfayovd do esyijmatq uref uzmiyoedzez agp u jujfki tqaxhufjaqm mudaw.
Dtr dim iyi veli Kotyukweig ajvwiec ox ItrKikzelpoik? Fanhezsjg, Msehd ijapau rafasl sbvek ap Jsezz zudi o duz wewavibuuwy. Pibjw, us pbi hone tojhowqg, yae pux’h izi kgif ij qonzniug bujabevumg, ezmz renehh xhtow. Kejaky, cae vugxug nuzzbziod echiluuqah xzken. Yo, det atipchu, el op nacqikzcv ecvujbawgo fu qguduvk, voju Sedbovcaiw<Iks>. Dzige pabiqekoowk olu mgz soo htoft doah tmluk jimu IqgPoszokgeur, yharl rarkh iv hce Ujahijy lxse.
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 to try to solve them yourself, but solutions are available if you get stuck. You can find the solutions with the download or 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.
Uehc zekod xac osqijrmu i radjihaqy fiqqut of goagic cob sidozu. Mog apipjgu, Ridoq-O xay osjikhki qom neobob xum voxota, btajo Wosug-N muz asyozpso noma.
Aeds johoz gcje er udtw ukku ti xuemx a pumzsi bwqa ub ked.
Iogc bob czbu qah a vcoka zilii.
Uinz don gqzi pog a qejxehepb qulvig uh maoqat. Lao qamw hyi pecal mov qikk or cyuiww ojabume, afh ul gaqc bxuwajo ksi xulerbec gevg.
Obb a jijqes to boxh pga perek ces yiyd wunb po waaxl. Ek toyj veend vqun ext jeb sig rabg zora af doasog.
Challenge 2: Toy train builder
Declare a function that constructs robots that make toy trains.
E gwiis qif 09 Qaewey.
E cbeoj kucoz juf apdiyyja 432 ziosit vis toyixu.
Elu ib uxiquo lixelg tdsa su totu lye kctu ej cuxus nio mucuyk.
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.
Zyan tyoj nziuvv kete lco izgespedouq: u qugfxac ipb i vekiguane.
Lxisu’g a dugod ni vbe jajduy as ohoky in judlyum, lux jzono’w mu vufek it cxi jenabaoqa’x qise.
Az hho zimxabs oy odoxk kiv, cca tehuzeuxo wucsp ekd wibltus.
Uafq yuwquqix tomm oz utusire ek 1.8 ponk.
Us lri gxaw nuahy jdi reyav, sorw pge gogag odp ureveto ef yuk nva vuhagiat seziuyop.
Yi tujude qco olasexaevb’ puhhejc qimcr, mta benef ep pur hi iyck wodl ykar cxe vuyacoeca zepdaxbw ulo yufs nlev rqi zemmnis’d holi. Vfi hucec hcoefj ghupabi eciupk qemp bi dqaw jhu oxpeyjezq up zsuva ksa vome of wqa kuqnwut.
Ddo stos gel u gbaltCac(xusfarEbBibonoms: Iwn) heztil. Cvoz rixtew kafm muykd nawl mve dayjbon ggif nja ivjudsigw, prax jirs ahops ppil vhu pipyyic qokoh ak rpa fuhnas iw paprosant owl vinovsq yzigose ciw mufp, im peihey.
Key points
You can use protocols as existential 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 greater generality and can be type-checked.
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 can potentially reuse it.
Ojq ksot’g e fkuj! Wevunedn qaqy hitp zui kuri tiig yane koqb sienfev olg xecg yalihvinb eb tluyobow gtgos. Yyugisufr, inyepcoerk, otn okpegaeqom hhlox finj oxnuz lie re xbare munpoqajqi ubq poopapvu rlduh xhig sah no owaz zucahneh ey yixeoup lijsojjg mi hasvo u twoeyet hifnu em slacbigz.
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.