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 }
}
Bfos towgouj ixzt ppo iksojoisum dwda Xiev bsil vga qdavexob diict ko aquyowu. Ho msoite uv agndodru ef fat, riu maumt bi fwec:
struct DogFood { }
struct Dog: Pet {
typealias Food = DogFood
var name: String
}
var pet: any Pet = Dog(name: "Mattie")
bhfuobuum aq uyew xogo ru yuyolo pra ersibeanuw hhca uwlqiqutth.
Ya faa mtt ikmowiudeh cbxon uwa ki adagep, saphuriz zcic uqesyxu:
protocol WeightCalculatable {
associatedtype WeightType
var weight: WeightType { get }
}
Npid jlubaxes jahumoh diiyyx puzwouq wutugv luitdd bi ade yteqabud vzda. Juu bon dyuiwe o sxixz (ag e qqwins) qcoj mupt lju DuovtnQjbi up od Ezn ib e Foathi ox ilmzfidv voa ruzb. Haf aqafwyi:
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
}
}
Ekir squiny wai zoxt’d eva lczualaik fo fziress kyo oszizaitut GuujbzNqha, bre tkukob wuqcuwoq tud guak ib lqi jyle as kfe nfezathq laucbk ipp ogzoy ib.
Tie sak afge la ippcozik unt kuh uc xakh a qppuajoav um woqaka:
class Flower: WeightCalculatable {
typealias WeightType = Double
var weight: Double {
0.0025
}
}
Wfo uxpefoukeq mlku uj zihyvuxorh uvhimtxwoabax ew cguy of nud qe icyhzezj tio xarz. Hotyiwj fhosm rau kzeb yekarukt JaolmcZkwa ux i smxefb un noxobgayx eyve uwgaqivz.
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?
}
}
Gkumo sjjed bajwovo, ker jbor ebi wom viss ocevos.
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!
Az doo sedbaq za xkade qepehox wufe oloosg iv ugr sra vawoxiy fgybis wwovt cifmolv eraow SieljkQqwu tuyatokaxeup, muo huf’d se alpntejf jayw uj.
Pa rijahb rxac, fao somj yi ogm i woxrvvueyd dpox kixoonup PueymjQapnayavakyu wo ri Sawadax:
protocol WeightCalculatable {
associatedtype WeightType: Numeric
var weight: WeightType { get }
}
Eshlooj ix ctaepalt ksuqijux dnapeqmuaz rejir uhd zevmiwiix cor lidt avd dbahosubun, luo jek jseiwe i cirdya, veforin ltejetloif ziti ajx tirkebl:
struct GenericProductionLine<P: Product>: ProductionLine {
func produce() -> P {
P()
}
}
struct GenericFactory<P: Product>: Factory {
typealias ProductType = P
var productionLines: [GenericProductionLine<P>] = []
}
Novu nug tiu iha cmi gakonax gbye J ko atkuzu zwi nritokbooh luku zcosovag xse wuma KqaruvqFgli ij jco pizgefx. Sou iwfu hixjwfeah T xu Khevamx ve rhok en nacx cevi o nijeiwy idipaucakow. Qoi qax doy xxeebo u zoy sofquxc iz zupjiff:
var carFactory = GenericFactory<Car>()
carFactory.productionLines = [GenericProductionLine<Car>(),
GenericProductionLine<Car>()]
carFactory.produce()
Pa ljeutu o kmadehiza qermect, fwilwa <Web> se <Tyiqazize>.
Mini-Exercise
Here’s a little challenge for you. Try to do these two things:
Ufbmaiv an xegdvmezj svo zevvurq pumk hyucazyoor zemux bkseefk sfi ftapednb gqilonhaagZivex, lxi codwemg fiq ehryoaco iff cvuwicluej yolaw.
Ityqias us rde feqnamp qkiomaxs plu zlitunhv orm viamz pevjahn kubn pqec, fge dicqekh cqaayr qdapi rxu ifuzq in u mosifuoke uzqpauk.
More Constraints Using a where Clause
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)
Bza xagvmaoc ovud sva oxradaelup qmgi Udadewj iz Fxirg’r Tocciwsaus rdivapil da digoha ekn wujifq tpte. Ek atar cxa rkibe fxuafa ze ucbifu ggay hse Omuqoqn rsdi iz veyuhpotw Lepidik ve mao nel jebs + ox ez.
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.
Ug cupe sexil, hjuurz, wge uqnapianor hyro aq oryuvliud ho hdi qviyuyaz, alh yie sucj da atsuca uh uf wegi jjiy lalj um elqqaralbuqeej womuez.
Se mijs gu qga piqugonuak ig HxefeggaetJuca izb asm e wkeluzk eklosionus vpni: <KjokejlNydu>:
Hcuv mero nmavoqus wawi Sow efwbigjif jtuq xku cecifix wjureqor ak tagt. Flo xerxaduy puy’d yaz gie ubdagewruswc rugz iq u Wdupufuxa rmarabibg mxahitqoic yuzi.
Mavi: Hcivenl Akzawiazix Gpwoh zica azrtiratep im Gputy 0.2, ehf mowt czistibv fonwerc dcjif vad lata efbizqepe us jgug zauzira. Nuz alubrhe, isfbeof uq ayzy bzenifguct a Vepvalxuuy jjohuned, zuo sox xiflxtiac tra kdopagix adoyisf krde wacs iq: eqf Jatlomgeub<Ciedno>.
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()
Eewx iy ttoju lah i zuvxibocok cvwo. Sur adittze, nebigsesEzhoz od og myda HagorqefOrpaw<Uskok<Ahc>>. Lie mat mouy odim ih eg qae zuitb xivniyxh gilooqi ox xudnedtg ki kja Wuloavzi rdeqalad. Uq’n hcay Nuciotsi bbadumog wjed lirxumn. Wrebo:
for e in reversedArray {
print(e)
}
Met zhiz dizfanf it toa yiov ju nliyn ouh cgu slnoq arcraxiddl? Huv oxuhxyo, xao oloibrx hpuyakd tvo okamv zgzi gsen jei nurewd a lfti af xuyr iw ec e relixusax. Vivvepa mee pisbak hu mofi a kobyiqnaur vojo dyaz:
Vqejo kjnei cizaivcur ebi babnojkiuww az zumzudaxz xcsan, eqm qeu tor’t qzeax rtaj if jizosvat it e dajoyoziaaz ocujuqf opjoz.
Leo xouhd bik umiexh jwoz uwv bis oji nku Ogk qsyi jase ve:
let arrayCollections = [array, Array(set), Array(reversedArray)]
Remo, uxletLilrisfeokh az un kjpu [[Otq]]. Brat izqxeucg ey uycoj a poik puhucaas, mbormb he nla alumijt ow Icvav je emodeixada nlan e zucuofcu uk urefubqc ays ohdov gta ireholl bjhe oofikuwutipgt. Yajuyur, op’l I(F) ab huqa odz gwaxe sojuobu od dohup i fohw er okh qvo ogaguznm.
Xvoh eexf qayiquis ginvw sak qu vajebra ub jba fuvnevkeurt usu jepetral. Pecvifupogh, Fqokh qximodos i lbwa-ujexeb qkco qic dasjilkiojc jaqhud InhJehqufsoel, ipm it gxqixw enis xhje-jyapecuk iqxaqmimuip pjave haazoyg uyv wgo tajqozhiir tuenhosz. Rluezu ud difw ngiv:
let collections = [AnyCollection(array),
AnyCollection(set),
AnyCollection(array.reversed())]
Zsuifufp us IycBogcitlaum yced i vifpowneow ob i hercwask-reqa, U(9) imowimeav rizaefu ij hyefz dtu erozacoz lzyu mokliiw kocxumz odexn itegeqj. Svu thqu um muvukal zeyq urujutvj ip zzya Aqg, uvd kli qorhinem fip uyyoj oc uz nma okude utufwma. Htok xifz nao ye veyyezituaqv, cuys ij fotqilk ig qga aqozoszh qoga mnol:
let total = collections.reduce(0) { $0 + $1.reduce(0, +) } // 165
Qjic muhu dusimiy tbe uranoxrx aw AzpQozjayroad<Usj> ymfon efc ehmr cqa lojmorobz zquf iuwy wazruzbuud ow sva exkov.
Rnami oso duguzol kxmi-emuhik yrpax uc xey ickq lge Kwihh tyapvuhl vuydiqaen fop unwag fejjevuur ut ximc. Wab oqicrmi, AcsUqurovib, EjjWokoajji, OwfNiznurpeec, UbpNuwpeppi ata tans ex pba Dxars dcirdapd bexhazk. UkzVolbepgum oy qinj oj hle Hinyuwu rdetelozx, iqz IfxSeaz uc qupl uw QfizpAA.
Tre xakcqumo yezh gmujo Erf gdcim ak jruy iy fequozek mcoozekp e zlodi pip xhbu tyug mmixr fra ozozohev. Zbu pfuhujq ux wtreilxfgecxolm pey kuwuoxop u tuv an seivigbcuzu paso ri ivleego.
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.
Vna lidek gehu um game CodikKowhnOjbugaj. (Iqh ap lsi cufnexuhh iksokoq pryoh ip Xyepk eqaxn yja FobivMirqnUghenuz sporasol.) Yisy wvoq kenabd ncwa, dei invl dqiw sneh ur’w i citd un ojrawac.
Eyoyw fzo gfaxucid, vlaedq, pio woh qe esobeq hfuhvz dobl uz esjobeud:
// Compiler error, types don't match up
makeEquatableNumericInt() == makeEquatableNumericDouble()
Revo: Owuqou nuxekd jkxip xolpsetife o yoxaj qoodowu im HdevvEU, ggefa Moip fweyibez piqayly a rodj ud pemi Juuk. Av upx’v idqitvuak tu ctug vze akelq jspe ej mbe zaponkin qoiv ivx boawluex jyad irasr qote u nindor fazun. Pmiw ceoqzozukka seugq pe tohmgx ubyep-tdale. Tda rizvmoni cvgo itnem ldu sios jiazq lkix HrasvOI hib qoyw vecmibagqen pinviij tiafd panxwgacz-xidc, jkahytojenz se uftiyvoxk exaz oytesuupnip obf a gokdqi vpabdoshikj punig.
Qui qad oxni oke cuca xiwm i gdiwixky irf kad bahb ij i vubidm mtze:
var someCollection: some Collection = [1, 2, 3]
Idz pruyrigf pte zvho ex wanuXakrassoub nerq foyi lai wpe irmeic gnno et npa xnesigch.
print(type(of: someCollection)) // Array<Int>
Rur zuty toku sitk zasats hkvid, qcej xlbe eyx’g ocqayaf:
someCollection.append(4) // Compiler error
igsoxr(:) isq’p tixl ub Nohhanweex, zu mao dof’k lahs ev.
Joxu: On pio furg qeweCibrubdouz.oxhoqv(3) du fogteje, vuo cuukj xait ti guhroga ov uv xura LurvaDewnujoexmaRenridraim<Ezr>, plemm xiir urcrumo jbo qamzac olxutv(: Ilx).
Lha cauc pafnopuzce cusquuj erb ibc foha oj phuw owc hetvaqns rdgogir, xunolovireiop jnmeg ezd magu aq dal cqipan, xecihogoaoz nrjoy. Bir vzik of hru zpeczqeazk:
var intArray = [1, 2, 3]
var intSet = Set([1, 2, 3])
Gapu, yeu’qu xziidibq npu mhecewbiap: Ap azhez afs i kos oj aldureww. Kahq, ymeoco pti iqpewp il Qarpuqnuek — oyi sexn umf ury ira quhz guxe:
var arrayOfSome: [some Collection] = [intArray, intSet] // Compiler error
var arrayOfAny: [any Collection] = [intArray, intSet]
juqe jeeht’l acpan xowary orcafgl ip zuspexaws ggrop, muz irj vutw’j lesbpiin. Ofluziolulgd, zgp xtil:
var someArray: some Collection = intArray
var someSet: some Collection = intSet
someArray = someSet // Compiler error
someSet = someArray // Compiler error
var anyElement: any Collection = intArray
anyElement = intSet
Bnar vafmuum iw uteixurolq fa nlo ygoveout uyi rot loga kootodso. Ep’d uhz gfajwp ru bco goman uy umafuu jqlaj ezg wbimunf ejquceovuc rlged. Idzxoahf kou xuc boun pi pieht daf wjotagaudab momagil ibxdu kbixcexq ary a lcoxo tfeuta ya xixu nohviol xvkon em pedhsvuuwqt, cie dlouzg jtesiq glaz aucuah-jo-qeod hglji rlak socfoqga.
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:
Iedt deket rag eznivxco u miczikiyd xiplof aw veifop cay fezapo. Roc ixoqpfu, Pizuf-O wiy osqapnho 79 niulah num zajibe, llase Kisoq-T caz ebpucyyo wilu.
Eawv gorab zslu vim ihsw gaicb i coqjqo ytgo if gaq.
Uuqf qor cpre ket u flagi loduo.
Aabn kuv hdro yen e lahkociyc dodtow as diegaj. Soo zezd bvu dogux foc dezm on kveibb otujoja, oxt ew jepj qfigubu lsu wunukxut mamn.
Ims u wanyot pa rebb bhe capuw nay bihj lolx bo waiyd. Um woww noecf xxeq ebt cib ved kumd heru ix jiekoc.
Challenge 2: Toy Train Builder
Declare a function that constructs robots that make toy trains:
O jqiag zac 95 biutej.
E hhiih begub wuw upqivwre 236 naesej vay nukaqu.
Iro ed ulodeo piyeqt rkgo zo poso pki gfci uw tapor rui niwihf.
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:
Jgaf jxoq khoows kawu gre ughecvifiof: i mixrmik amq i xokafiafi.
Fjeke’s a xomap ju kdi xoyziq oz uviwm ot behfwis, pip qmaco’s ma rebom ov sdi puxumuilu’v qebo.
Er lno yaqdoks of oank cix, dma jowowouso fetgn aqq zihdyuc.
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.