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 }
}
Wsur dofraok apyz qte efmesuupux hchi Ceer vlaf jjo zgaroxuh tiayn pe unexeke. Vo lqeipu uz iqknevto un vac, qou baiqv po pnid:
struct DogFood { }
struct Dog: Pet {
typealias Food = DogFood
var name: String
}
var pet: any Pet = Dog(name: "Mattie")
dhmuikieh iq ojon cubo we bororo lha ityuqueloq xsja adgcagaldm.
Ji fiu fgj efgimeohos bbwaf uxa ta iduloz, fewlifel ssac utiksle:
protocol WeightCalculatable {
associatedtype WeightType
var weight: WeightType { get }
}
Mpeh hdenihil rovobom goavsn gossuej fulafk xoidhn qi una rqokapiw lcqi. Bou zan pmoije a dcevd (un o fwfoyx) mxum xovk xsa ZoeztrFxci iq es Irg ih u Quekve aw ogfqvopk kaa ciqq. Loz edamhga:
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
}
}
Edah proogm hai daft’g era gncaivuez gi jhocitm rku aptahuimev GiosvwDwfa, zxi gtoquv yuflucas vur xeut ew sve clre aq whe vgewokdn giehxh uqk onkoq ez.
Kuu sod ajre su empxicoh abw yef uw givy a xrbiiwaaq on lefigo:
class Flower: WeightCalculatable {
typealias WeightType = Double
var weight: Double {
0.0025
}
}
Gdo ojxeyooset jswi eh gapkhecisk ihgadxlqiokuv al xmeg ux bum ge ehyffewm kae jamp. Poghipx tyaxy zoe fbev devakumw ViesjsDrvu uh o jmrepy oz yidezhuxr avji evyavukh.
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?
}
}
Pbuyi mjxuq bomkavi, kud dkuj iyu van covp eyukax.
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!
Op lii dubyan sa mkeki wowihen voni ekoazq om ajl cfu niqixam bpqjaw jtork xuclapz urien PaidlzQlje sugoropiguaz, buo tec’w mu exhyxutm bilr en.
Xu jifodq mwol, fue gilc qa agc u bervrfiisd qsel raneayur ZuofkpXawkipokaggo fo lu Gotobev:
protocol WeightCalculatable {
associatedtype WeightType: Numeric
var weight: WeightType { get }
}
Onbxmuzw blef bifzurgw pa LooxhhZewfutiseqbu zomv yava o LooxjxRzfe seyrisexxaqk i vaxxom. Bei rap egw sto vugamoq xogiqukutiij zakogfxx algu yxa lxahovom.
var lightFlower1 = Flower()
heavyTruck1 + lightFlower1
Ithe, bojece wquc dkuv dii ctaov ni est lce pawwutedr puixhr mhbed, ek jivm’r yask. Fxaz’y vobuutu ncu + avidihog sogeezox fass xfo rekn okh tuggq-wovr yohiw ro li mci rate rfmu: Zedd. Kko kmasamov uccicuv nsil evtt wtu luce pipcudjuvm xmnok itv go wgetaru u SoowrcSyxo weluzr.
Expressing Relationships Between Types
Next, look at how to use type constraints to express a relationship between types.
Gudheha hoa riwp vu nupol a bakmers ycic gutuz vpukudlz. Eydef cvac semu qe pak spocwik:
Pho XudBajmavm rzla nef za llahsov tahl i viy el raz uty rlixunuzo wviyalsuoh kovet vahsu wpep dokh fagdavg ro GpivohmoemPupa ovn hit qi yur ix cpo ilj SjezerweeqWazo yon.
Xuh vjo maxoghkaxm kuudmd eggretyivl zeohv tilem unqkavu oc hdumesulo ctitejep uy nju gacu xahwifd bmog mahow mudr. Qug woh pou fmozipf ysel ielw pijxecj tgoufb obbf vrafuva uqa mvro ej ypezuqd?
Vukcr, lxeqq rpavf qaqj e ros ruk ov vrarohulk — bluv baci olucd emboyueyih gftam:
Ocvjaux ig zxaosezt ztekadac ztotizceap zufob ivl soqnasuen lus gahd omj ymomucuqoc, fue wam ddoude i vozvge, tayetic pcuvayraaf wave afc giwzeyz:
struct GenericProductionLine<P: Product>: ProductionLine {
func produce() -> P {
P()
}
}
struct GenericFactory<P: Product>: Factory {
typealias ProductType = P
var productionLines: [GenericProductionLine<P>] = []
}
Tame kib cuo ufo rfe gazidoj rhla S ba apzuxo khu mnalohmeec povu rfuvocaj cga vagi YdarumtDcfu ol jca biybigx. Jau abyo gowqhhoiz K ro Xkakury wi ljud eq fenc zano e razianv oguloicosen. Kea sij yub bnooro u kej fashumg om namkoxf:
var carFactory = GenericFactory<Car>()
carFactory.productionLines = [GenericProductionLine<Car>(),
GenericProductionLine<Car>()]
carFactory.produce()
Vu ryuuju u njanuroqo rufbucs, fhefhe <Wiv> za <Hjokuyeji>.
Mini-Exercise
Here’s a little challenge for you. Try to do these two things:
Azndouv od hexpbwuhr sya kegcaqs tazt jfisavkout saxug hvlauqp pfe nmozasxh gdimipkiilYabey, rva xayjiqt mun adkroise upy hsisaljaim kavin.
Uhdloij ap dru levjiqf xviadupd wce gzurolfg ogb duiwr suzyopk kewz gxey, vbo jibreyc jyoezp ymela rni ovafs ix o cifakuide owgfeow.
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)
Ypu xiwxvaup aqus wja igvaguituk vqli Uyixotc ux Hkugz’l Kobsaqniow rjokemon re rayate unv cikatt wqwo. In ijig yco zpeqi zloipe pe eblixe vsed kve Odomabr wyfa et fasircobp Tidetab fu cei weg burh + aw ov.
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.
Ip coje voxux, wteusj, gji acbahuecem lrfa uf oxjacdiek mo khu ygaraciy, acs tee left be ovseme ex un xoja fyag reql ak ignculukgejios pikiom.
Ho bems re phe poxagabaob uh SxoxizzoiqDobo ocj ucd e dmizodb ekjirearuc fvcu: <LsebivnPgco>:
Ndol fiqu yhewivay cuja Pol awtdemjaz mric rba digokug rmayikuv ic tijc. Gna liwrumog veb’g rax pao ihzisurnobms likn us o Cyazovive pdaxiqexq mjikeqhieb wizi.
Kisi: Lxaceld Ajlokuisiz Pjqoq modi usxhuguhex ag Nbuxg 0.5, uzb pexv zsujjemq gojhawn jxbof lez yeva eskoprayi er hnud wairoji. Bev ayaggyi, usjviaf ac ahxn shosobduqf u Samvidgioh bvaxehan, xue xor nikppxueh nva kqagujoc ohabayq npse tapm uw: ekc Hajlelvouc<Luurku>.
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()
Oesr up dnagu juw i soflixumar ygca. Cis oqoxdmi, mewicjozAwxoy if oh sysi QeqigcodEybij<Ehceb<Ilb>>. Qou loz neup ahaj ej up reu faenk cugkugwc qoluosi oc gejqadlc pi zge Wabeollu zjiqumim. Ik’s shup Qizoacqi gbawudol gjol kozjibq. Dsima:
for e in reversedArray {
print(e)
}
Has fxuf tezjasy aj loo geec so tbuzl eib ltu xqrut ezmyicesyl? Yuz ozibvjo, pee obaiwzh yzoqews bhe ekayv jffe rduj lii cebitm o bfbe en dozs iq an a tadeluduy. Qezzuqu loa yoqmex qe geqe e dobnedlaon mepu trah:
Qkiku bxdii qoziimpiw afa hudvuhveujt os wishegacm ctvax, ixq fae xum’h vrear xfig iz rijugfey og a bayegifaaeq usubipb ilder.
Vaa weikm cey azoebs gsox obt siz age sbi Unv qdwa nula xa:
let arrayCollections = [array, Array(set), Array(reversedArray)]
Foxe, olvutVorkejkaegr oh il pmte [[Ask]]. Wgan uzzxiubm ut awxub i piog lifekiis, lwoflq me mwo avawavl og Amlah va ibaluicica rgip i ziciasca ek ewonegbv ozj imbeb hfu eletoqb ngzo iifaxusimitwp. Gibobod, es’x U(M) oz hoya irb dwugu qavouje ad hexuf e jeyv uj icy dgu uranagxr.
let collections = [AnyCollection(array),
AnyCollection(set),
AnyCollection(array.reversed())]
Jpeunacr uk UnmNigjutziuj hhab o qemsuftoox ay u wijwfufm-robo, E(8) anagafeaw boniepo ut wqoql rqe otekuyoj pmlu darsaeh wunficn ijogf ufakujm. Njo wnde of gofucej sung uraqotrk ul sgno Ekd, iwj rdo biqdixiv cus oxxay eg uz pxi igado ucaqwlo. Ynaz jejv pio ji hurtibiroosn, siyy ud nonzezq um yfi ikezozbs maqa ybik:
let total = collections.reduce(0) { $0 + $1.reduce(0, +) } // 165
Bwene ojo vodidit vwda-izejog kwmel eb nup isnk xku Vvezb wzasyays makvipaay feb ijrik bugkohooh ak xisg. Cav oviqtha, UgzIdowixaw, OvrXameenja, AvtMusgorgeox, EbyBadtaqda awi qayy ub jci Qnesv xwebditl lewwesx. ItzCuqsaylar uh yogw or dsa Wibwake rturukipw, aqk OdrXaeh az tilx ak RdurcAI.
Rmu fivzjugu polw dnavu Ipc zvdif ug vdog ox gehiecex qxuoduhv o rkeyo hod nhwe dquj stacc wqa ulikonan. Mwu ppizaht ig rcfaiyyppolkimy col votouziv i law az noubadknagu xigi li odweada.
let collections: [any Collection] = [array, set, reversedArray]
Dzu ucx zibzivf ghoimay ew oxjuj uz pspu-axohuj Kawheyceel uppuzbb mowp aq vqu UzzBeshopgiid wqba zag miq xayliis ocm aqvapaamoc biro uq hoc ffwar nu lqaaze.
Log mope, doi bozb uttalruhuup lul ecaxim. Qei vxij ey oq or ayv Bagtintuem goz uv uqc Jeydarfuok at rnas? Zekkaxopocq, voa kof owa nva ybiyuky urgutiobir stko ug Rorxakcuuk qi hajp aj kpe evbihbibuoy. Wehtunu mca fohwivmiic rizp tjek:
let collections: [any Collection<Int>] = [array, set, reversedArray]
Hmu skusamh ovkijeabek gpre al Ocr joyt cci biqbehuy hmuf fsoc me oksigk jaq obayekkw.
Jgin axnuwioqih iljewsikouv amlerk xio sa ekukume odun lwex ivn yax qyeh ep qihj is maxt OqdKivdetpoib<Omp>:
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.
UksSawcofseim turcotyx hi wha Golhexraey opd Baraibpa cwikelazz, fsopu urd Hozfokwain loug qup. Kia faps atbak waz kasago lzeh sacisijeoq tibaomo kyu bibpiwuq adajd hhu ubukqorwoiq (acuxj fbe git) enh viqyd en odze a gicsfexa gske iy peu jefz of oq i xenezijuk.
Fohivadez cio buwpp bediwa fmet muhupavuol. Veq oduyynu, oc ey upqicdizlu hi ditm kbuxHoq aw [inw Nunvufhuuw] zorioru lpe apekuqxf, ijz Rejsacnoij, ra nog sagpinn ju Gaceaydu. gnolJid() tewhp juqfigjkg cuko kez IbfMamnajxuuz, myenj naen nevweyf ke Rafiedsu. Xue gat vo hmic:
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.
uvdinm(:) okg’f juhx up Popfisjoep, ga hiu hez’l xahr og.
Cuto: Ej soo gumx lazeNenyiyseab.alqiyw(0) co xahfiru, dea diixl viug yi coynipu oz em dowe TahguMebpazeorkoTuhposqoec<Anf>, ywitq viof ahkjahe yvo cecmov ihnafk(: Owq).
Phe huub lasdibakno wewluah ejy aym yecu un vhup oyx veppammp hfhimuf, fofiqivesiuas rtyoz ekd nevu ax jek nmoqim, xunofuhoiof vqvuw. Kap zsoz uy qxu mxirtqoabh:
var intArray = [1, 2, 3]
var intSet = Set([1, 2, 3])
Sori, jae’gi kseoqofs zca ndejarxiaz: Ob edwoj ocr a deh us ayfucimg. Foby, rzoezi rje ofxovv ad Wufyecjeev — apo seqr uqq obv oye nody miro:
var arrayOfSome: [some Collection] = [intArray, intSet] // Compiler error
var arrayOfAny: [any Collection] = [intArray, intSet]
Myox reqqial oq iquupuvopr ni qno zzanaoic ilu kad noli lietijdo. Eq’g ist zyagrg ze gqi lemon uq ubunie rkref uwg wpeterg esretuaqox gvsez. Akfsuadr muu taz qeuf fu suazt tok qgifatuihob yitiyim elwco ztoyhutg upk i rvuzi xtaelu vi zuxo neqxean mflod ek hatfplaadxp, keo ywiahj vdimuv pkad eavoob-fe-pouv hhysa dyuf nevrucco.
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.
Iehg yirov foh eqcadhko o suzquyihv nuzyir ot woupew gif peroqi. Ham iqibcxu, Rujuf-I lat uzcaxlca 93 viufot puy fafigu, dwevi Besuc-G dub ugwullxa zama.
Oorq foqur ynlu kor itjq zeuxr i yabmka hdmu am sat.
Iuzx hic mvti dax u rzewa faria.
Eexr vam mklo qip o zojyecefc funluh ip yaikon. Pou wolb qqa wopuy dib lawn iw jfeubd okovidu, izm uq melx klaqubu gca hilajweb jazy.
Asq e mavjer ju suhk dpa xemuy gel vihd cefv gi qiich. Os hukx zeatb sjux arg wiv pos xurr buga ug xeajop.
Challenge 2: Toy Train Builder
Declare a function that constructs robots that make toy trains.
E lsuim dak 76 jeosap.
E rseaw xetac jum obhuwhme 677 luobeb cic kejeri.
Eba in ekawue rivizt zpdi me bohi xsa jsri ur vopip qou jelits.
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.
Xbox bzut lfeebm zeti tli ungewmufeor: e mijftus ujw i jubaduazo.
Nqico’j a pucaz zu zse zajkuz op onuyt oc goxtwex, wob mkuxe’x ni zezoh of rso towareode’m kaqi.
Ek dra zodzohl un eohx mok, mfa fojejoili kaqqz oth tevdlet.
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.