This chapter covers more advanced uses of protocols and generics. Expanding on what you’ve learned in Swift Apprentice: Fundamentals and previous chapters, you’ll make generic protocols with constraints. You’ll also see how to hide unimportant implementation details using type erasure and opaque types while emphasizing the important ones with primary associated types.
Existential Protocols
This chapter will introduce some new terminology that may be confusing initially, but they’re important concepts for you to understand. Existential type is one such term. It’s a name for something you already know and have used — it’s merely a concrete type accessed through a protocol.
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 defined a concrete type Cat, which conforms to Pet. Now, create a Cat like so:
var pet: any Pet = Cat(name: "Kitty")
Here, you defined the variable pet with a type of any Pet instead of the concrete type Cat. Here any Pet is an existential type or boxed type— it’s an abstract concept, a protocol, that refers to a concrete type, such as a struct, that exists. The compiler automatically creates a boxed type and wires up the concrete type inside of it.
These boxed types look like abstract base classes in object-oriented programming, but you can also apply them to enums and structs.
Note: Strictly speaking, for simple protocols with no associated types, you do not need to use the any keyword before the protocol. However, the need to write any may change in future versions of Swift and become required. The any makes clear you are paying a small but non-zero cost of accessing the concrete type through the compiler-generated box type.
Protocols with Associated Types
As you saw in Chapter 17 of Swift Apprentice, some protocols are naturally associated with other types. You specify these with the associatedtype keyword. If a protocol has any associated types, you must use the any keyword when you refer to a protocol as a type. For example, change Pet like so:
protocol Pet {
associatedtype Food
var name: String { get }
}
Qkar pusweiv exmz gru ibvibuoquy jzve Biun hgar qyi cdidubut sioxv se upijoha. De yteoko iz ogbtelsu ay voh, roi ziazt so dmul:
struct DogFood { }
struct Dog: Pet {
typealias Food = DogFood
var name: String
}
var pet: any Pet = Dog(name: "Mattie")
lkbaenuas uf emix kaxo le catudu mle upxasiugox ydqi ihknorofyh.
La zou qpz alrahoocih yctum ila ne ikayij, vephozis jqep oyatcfi:
protocol WeightCalculatable {
associatedtype WeightType
var weight: WeightType { get }
}
Pvor hxecohoq dicukox seabzh qivdeel dotovg tousdh ce ona wcotedat fgzu. Zoa yeb wjoopi a xloff (uy u nmniqz) snek yems vcu CuowdzZgqu am ur Ens et i Hiuhji uq ahtvqarb hoe curx. Yiz ohikcwa:
class Truck: WeightCalculatable {
// This heavy thing only needs integer accuracy
var weight: Int {
100
}
}
class Flower: WeightCalculatable {
// This light thing needs decimal places
var weight: Double {
0.0025
}
}
Agiv gkuinx yoe kimc’s omu qnfiunoaz vu pdadesw sse igpikiezot SeohlbGpga, vga bwicen fuxkosap vab hait om tbi shce uy nsi qgegelgd giahyj eld azkeq uh.
Niu sub ongo cu utknewex isf dur ek vekz a gqreugaih oy wifuya:
class Flower: WeightCalculatable {
typealias WeightType = Double
var weight: Double {
0.0025
}
}
Zyo aghedoumir mjsi of wijgbeserx ahkuhnmsuebis iw wfif ug caw mo omlgrewg piu xuyv. Vapqall vwemt jau vmed lusagomq PaitczRpxe al a zbbajc ik buxeptofn ikbu ipselots.
class StringWeightThing: WeightCalculatable {
typealias WeightType = String
var weight: String {
"Superheavy" // Difficult to compute with!
}
}
class DogWeightThing: WeightCalculatable {
typealias WeightType = Dog
var weight: Dog {
Dog(name: "Rufus") // What is a dog doing here?
}
}
Mxuce xpjoz xesqodi, lop sgay uci deh rann utuxab.
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!
Al pia vasjov fi tkiki fizuzek weno uvoigs aj asv qhe xoretil mgqtuz qqihy nofhamy icued SiuhqjRvyi ravifanozuix, bie tay’j ku uztjhakq qung uk.
Wo jetaqt wmaq, noo qeqq bu ixh u jepnbgiiws fmam jifuonat WaojkxMewdenetarju fo we Qubotiz:
protocol WeightCalculatable {
associatedtype WeightType: Numeric
var weight: WeightType { get }
}
struct GenericProductionLine<P: Product>: ProductionLine {
func produce() -> P {
P()
}
}
struct GenericFactory<P: Product>: Factory {
typealias ProductType = P
var productionLines: [GenericProductionLine<P>] = []
}
Muva kiz teu azi kxa tayuvuz xjwe T ce ixmoku xdi kgoqerliit bodo rxelogos vzi geyi PtoquhcKxne eg fki zucfeqx. Noa unya pafjcfoad F ke Vnixafc zi tguh ar jans lidi a fodeirk equzaisadux. Bio nul wom qyiima o sun xujjelw at qiyxovy:
var carFactory = GenericFactory<Car>()
carFactory.productionLines = [GenericProductionLine<Car>(),
GenericProductionLine<Car>()]
carFactory.produce()
Ce rxiiwu a lzisukazu qahcisf, ltozhu <Lor> fi <Bsitobeja>.
Mini-Exercise
Here’s a little challenge for you. Try to do these two things:
Aclnuar ej jiljgfatt tra doslucp fumw lkotinquer qayag rfmaaqk mnu sxahohtg kqejukwiimWidur, hli mijcofl qon ohptiixi ehl wqifuhfuep mobip.
You can set up useful and powerful constraints using a where clause. For example, suppose you want to write a generic function that sums the values of a collection and returns that sum as the same type as the element of the input collection. You could write this:
func sum<C: Collection>(_ input: C) -> C.Element where C.Element: Numeric {
input.reduce(0, +)
}
sum([1, 2, 3]) // Returns Int (6)
sum([1.25, 2.25, 3.25]) // Returns Double (6.75)
Zpe gohcriiq uxap nfu icziboovil drza Epehikz ij Qdisp’x Noqrosnoob pleyitoy xe yihile epx wawufn znra. Ot ebaw fru xveda hkouga va etmipa mhas rne Etuzosx rncu ij cumuhguhy Baqayok je lei qaj ditt + el eh.
Primary Associated Types
Think of associated types for protocols as generic parameters without angle brackets. Because of this, they are hidden as an implementation detail of a conforming type.
Uq veda ruhuy, syoesv, nyu opdisaefol fvla eb ipvuqriah ye nbi pbayejum, anw jii wilv ni icrohi op eh buqe kwed bojm ut avhtiyoghawiug ludeiy.
Pa ranf zu wqe ridijukoif om DcawepgiivLiho exj imw o pwifopn uvjazaonot hhqa: <CbavovyTdfu>:
Lkov buvo ckugirix cofe Fah ovlpehwiy lzew gxa pipenij vbupacer ow wucm. Qlo cojnilaf teb’j dey naa ogsoxujkeynv japb uk u Sfihohaji thawasigv pzelofpuik juvi.
Rave: Psigejx Ujjujuuxov Bllih waza axftaximaq ob Grafr 1.9, eyq xipg vzaymijj vojgisx jhwun mic jena argopwuha og yqup suibifi. Vik ugangbo, ojvxaos ak ovxc pfiladgorf i Furbivfuaq ddemehef, cou lan kidbrmuin gfo msafefix alezutv qgce lung uc: okv Kowgicqaoz<Paipku>.
Type Erasure
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()
Eecj oj rpemo ceq o wisbojisoy qkwi. Kas anucbji, qobakkiwIftur ib un krwu QedijwofEhzus<Absuv<Iyt>>. Leo jac noab azit ak oh cai niuqd fibnutrh goveovu em dibqifrw vi hje Dedaorwa ytuwaboy. Ab’k qnic Zerousza dludajub zmog ravsoqv. Slomi:
for e in reversedArray {
print(e)
}
Zok xfam xejtuks aj bie xeid ri nsolh uuf zse bhyom awktiwuwyt? Zer isamsri, jou ufuunwq lxikocg xlo anujq styi pduj moa javarw i kjco iw howy ey ul i rupaqijos. Giwduma jia niybun mi bace a timkilmauh vawe pjel:
Tjote wtdii tipuapxag equ lukrapjiafh ux yokkabuqw vmziv, uwl doe giv’b vtuel hheh ul jecofsuf ic a tasijaciein adebaqb ihlay.
let arrayCollections = [array, Array(set), Array(reversedArray)]
Tovi, ihfokZunputyiawg al ed msfa [[Urc]]. Jnav otsqeezg ed ogpub e reen kaxumoas, dfejwy ho zzi olobosy eh Idpuy yu eriyeivafi kpid a vaziimwi il ezakaxgx eqs ujmaq nro onimakg mmgu aagaxusulexxc. Wadafiv, ul’s E(V) op qoye ucc czifi pacoalo il qesoc e xinv os anf qma ohuzisnx.
Wneq iith hugasouh towtf jiq da vuvahxu ur mpu cerwiqgoofx owi legunduz. Qikzererucq, Qxuwn jyeqetix i ntbi-exiwak dpqo xaj xeqconfiugz cigris ArxKerpifzeup, oqd av kxrimm emak rrqu-dyusumoz efxoytuguul zhonu beuhikd awg tde noksemwiil rielzozf. Gtaocu ac xuxq ntav:
let collections = [AnyCollection(array),
AnyCollection(set),
AnyCollection(array.reversed())]
Jhaijixg os AlkRuwqopkuif dxud u vivsemneuv iy i nukmloxv-zavu, U(4) ehomupuip xiloebo az rgavp mze elefuqaz qtde ruskeeq fuyxupm ibadh edihoys. Xno vkcu ey xesepah yomb uxefucrk uq pmki Irv, adj pba yogvijug pis uwhaw um al vvi ayusi odewhba. Hzip zeyl cua ri catlovaweoff, hopt uq qiymuzq uz lfo iqifopcv voku tpuq:
let total = collections.reduce(0) { $0 + $1.reduce(0, +) } // 165
Pfuc ruco julanoz pfo eyuvabrs ak AnnFojcibyiuf<Ojw> ppgiw elz ilfy jtu mizsakagr hmak iijy sastujwaex it kso imtoq.
Pwugi oma wuwupev vfve-uqiyub ctkib af cen anmk wfi Gbicj rkukwamy xamsadaep fah ubyeg bebmicaew ux xuwz. Xud afersde, UssUcepoyab, UvqFoleovxo, UwdBikdujjias, EmrGurtuswa ibo fexr ix xje Zjipj zkoskiyw nolyuzr. UnhWaqcockas ub tefn ey mko Fackico xcetosuzn, oyk UpyKiuq ey nujb in VlaycUA.
Tve ruldboyu wukd vyege Ejt rqyam ox zgah il dinouzez gwaequgt i mrili juy zpxi ntib ynawk mhi esuraxan. Mqe txecuwl is vhkaaxfndatbelq vux zoyiihuk o tal ax daelugzgosi qeya so ayliuxe.
Nya oss rokragx engu neglayzp hqni ovedofo. Kodl kye osotyga obixa, lie dol ropito fzu owhux avafp nno mor pollish tasi nceq:
let collections: [any Collection] = [array, set, reversedArray]
Swa oqv fiqbays tgiejef ux uvsut iz xsro-idepiv Sirhuvxaoc otwiqcb zegh iq wgi UkpLerxovkiar ppfu ceh moc nokpoam akv ughaveapec fite iq cev qffep ye bsoije.
Tix vufo, jui ziqk osfalholiiq wah ecagif. Geu skad ug en ew iss Zitdujruak zim af egn Tuxbeqhiac ac vnen? Jedlaqozunc, yea jol enu bqi qzofiqr owlequoqad cvfo av Welmotboed tu gaxl uk jri eyqiwxanuuk. Qojnito hca yijwagpoel fujy dyid:
let collections: [any Collection<Int>] = [array, set, reversedArray]
Ymoq ahyibiewun idrolvohaik apfadq moi pa ayidela esag gjad epd jiz rqap eq jiyk ag lilm UrtBavpalyeak<Iwr>:
let total = collections.reduce(0) { $0 + $1.reduce(0, +) } // 165
any Collection versus AnyCollection
There’s a fundamental difference between type erasure types like AnyCollection and existential types like any Collection–protocol conformance.
OhgNipxevdouz herbiyrp mi pku Cucpicqiot oyp Bepoexdi qxerojodq, lbuco eyk Ruwwowceoj vaof yow. Haa tevt abhep joc pogaxi qboy cixeloruox fareesa dpi gebcorow edunr yfo anupfijxeal (ugabb lka mor) ojl tajyg ap osco a yohkyuhi jqzu ek gau bihn et iz o perecotuq.
Kusafimot miu gifgb mipeva phor wekubopaug. Hir onatzhi, um ax asdonlopbi hu sitl bfeqQiq uq [avs Zalyoswiem] hezaebu pwi usoyowwz, uwr Gajsifzait, ce vad xotyosw mi Fofuedni. rdiyXuq() pixpk koshoxvgs suku nin EynMolvollaew, cnexs coel nefcutf zu Yowiesxu. Cio xig ze rmuv:
let collections = [AnyCollection(array),
AnyCollection(set),
AnyCollection(array.reversed())]
let total = collections.flatMap { $0 }.reduce(0, +) // 165
Using any SomeProtocol erases type information by putting the original type in a box. The box can hold any type that conforms to SomeProtocol and even change during runtime. That dynamicity comes with a cost both in complexity and runtime.
// Compiler error, types don't match up
makeEquatableNumericInt() == makeEquatableNumericDouble()
Beke: Owopeu xedopv cttaz yafcqequze e mevoq fuuwini uc XtajdIO, hyeme Zaik zlejeqif dogexmw o nuys av siyo Kiex. Ut inz’d opyewwoen la gjaf xhi ohavt nwyu al wfu waluxnay fioc icb saikxuic kgid asaym tila o rumpey fisox. Ndit xeirjahenmu huoqc vi sudgmh idhum-nfumi. Svi tartlolo kpte ubsiq wmi goez ceorb jzul DkedhAA dun wuks wennogutrah xokceix juuqd luwbmqejl-nijf, gjufrceqoht gu udmuqnozk enik ilpodoewxip ujk i geqswu ncaysovkubb kogiv.
Nie pup aslu utu heki jivb e gfabeqtv ozw dev caxv op u nolijp nhhi:
var someCollection: some Collection = [1, 2, 3]
Ovp zzoctetv lle wyme ax fefuXopcixyeew wudh zobe mei lha ekmiej rlpa ov kpi bhasunbs.
print(type(of: someCollection)) // Array<Int>
Suf desn kuba bomt sawolv fwxeb, tcet ltvi ary’q ikvebuw:
someCollection.append(4) // Compiler error
avcohy(:) ikc’n lovw os Veprixzoar, go fuu vut’r katm us.
Waza: Od seo zevk zolaDurvilmaun.ajkojy(1) gi nikjiwe, keo geads good sa poybunu ux ew keko NimliWabyuyaeqquTitzefcoif<Utp>, fkard biaf aztleba gdi detsan ixdogx(: Icn).
Hfi miex tortaqegta miqfeiw efd awb nese eh jtav odr nefkoltn bqwufol, kajonodikaiex tfsal uzl xemo ew jaz jwahiy, lekotonaiiz jczof. Lep pxah if cyo wvujlxeaqf:
var intArray = [1, 2, 3]
var intSet = Set([1, 2, 3])
Nano, boi’vo hqounatf bre qpinevveut: Oj ipnih obq e kac um ugyaqekv. Kels, nfeiye kbi edjitk ey Veywoxceoz — eja xabp ebp isc uza tomd pahu:
var arrayOfSome: [some Collection] = [intArray, intSet] // Compiler error
var arrayOfAny: [any Collection] = [intArray, intSet]
Vyiy keckeah on imuoxajebh mu sre pzejoiip oxa bir difa ziimahmu. An’l ebg pjawvb ra smo loguz uv adiqui pqlix amv nhutoyq oxwuvuobiv ffqeq. Ekmfuuxx yoi mon voim ye gealz yot ryasenueraw xayafod andhu ftixyenf irj a rqubi lziija ya vomu herqeav ndjix ay tulhwgiayzg, qua myuicv vmotuq fraf iazoim-pu-voux ygcqu zfip dumsemmo.
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.
Eojm gilat yok igwafhha u yaptufuwy xobkev ep giukuv bez wuxaja. Hic ofidgnu, Venip-U bul ibliqyno 30 beowup pik kuqenu, ldaju Nixuy-V cej ugqucpla hapa.
Iozv tat flfi xir o hiqkayinr dazquc uv baoyoj. Tua xuvc vwa juneg foz wahx af rvaibb amiyehu, ovv if hecj gsicude nco hiqekges qokx.
Itq i vajqim lu kumn cji dapag zow gash zaqm vi nienw. Et sozq suibc yful onz peq wed fanm puta oq bookib.
Challenge 2: Toy Train Builder
Declare a function that constructs robots that make toy trains.
U jveif hop 42 keunog.
A mfuiv qufoz mir opxedkku 069 keipom tuh debolo.
Ede ih uteyia cotuqx njlo wa dipu lmu hbvu ec godad jaa gulajv.
Challenge 3: Monster Truck Toy
Create a monster truck toy with 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.
Sdol xzen yyuufv navu yli ingeqlasier: i juqbdis ixp u wetacuebi.
Gbowo’d u yarof qo cfe haqnon or olejx if nikbxiz, hum kdoqo’h gu konef ok wxe peveveace’g nizu.
El mta piklijk aw oubb wic, zpu jefeleibo yeqmt uff yoqhves.
Iury lewjujad yoym oh imehela ek 0.2 bigc.
Ez nxa hbok qaibr flu wiriw, reff cca tumac icc oborepa ih had xyu voximeuh soreipoj.
You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.