Apple declared Swift to be the first protocol-oriented programming language. This declaration was made possible by the introduction of protocol extensions.
Although protocols have been in Swift since the very beginning, this announcement and the protocol-heavy standard library changes Apple made affect how you think about your types. Extending protocols is the key to an entirely new style of programming!
In brief, protocol-oriented programming emphasizes coding to protocols instead of specific classes, structs or enums. It does this by breaking the old rules of protocols and allowing you to write implementations for protocols on the protocols themselves.
This chapter introduces you to the power of protocol extensions and protocol-oriented programming. Along the way, you’ll learn how to use default implementations, type constraints, mixins and traits to simplify your code vastly.
Introducing protocol extensions
You’ve seen extensions in previous chapters. They let you add additional methods and computed properties to a type:
Here, you’re extending the String type itself to add a new method. You can extend any type, including ones that you didn’t write yourself, and you can have any number of extensions on a type.
You can define a protocol extension using the following syntax:
protocol TeamRecord {
var wins: Int { get }
var losses: Int { get }
var winningPercentage: Double { get }
}
extension TeamRecord {
var gamesPlayed: Int {
wins + losses
}
}
Like the way you extend a class, struct or enum, you use the keyword extension followed by the name of the protocol you are extending. Within the extension’s braces, you can define additional members on the protocol.
Compared to the protocol itself, the most significant difference in the definition of a protocol extension is that the extension includes the actual implementation of the member. The example above defines a new computed property named gamesPlayed that combines wins and losses to return the total number of games played.
Although you haven’t written code for a concrete type adopting the protocol, you can use the protocol members within its extension. That’s because the compiler knows that any type conforming to TeamRecord will have all the members required by TeamRecord.
Now you can write a simple type that adopts TeamRecord and use gamesPlayed without reimplementing it.
struct BaseballRecord: TeamRecord {
var wins: Int
var losses: Int
var winningPercentage: Double {
Double(wins) / Double(wins + losses)
}
}
let sanFranciscoSwifts = BaseballRecord(wins: 10, losses: 5)
sanFranciscoSwifts.gamesPlayed // 15
Since BaseballRecord conforms to TeamRecord, you have access to gamesPlayed, defined in the protocol extension.
You can see how useful protocol extensions can be to define “free” behavior on a protocol — but this is only the beginning. Next, you’ll learn how protocol extensions can provide implementations for members of the protocol itself.
Default implementations
A protocol defines a contract for any type that adopts it. If a protocol defines a method or a property, any type that adopts the protocol must implement that method or property. Consider another example of a TeamRecord type:
struct BasketballRecord: TeamRecord {
var wins: Int
var losses: Int
let seasonLength = 82
var winningPercentage: Double {
Double(wins) / Double(wins + losses)
}
}
Mics WijheqlovrSinipr ajd LelucaqhSufokp tube aqimwaxah efjfaxuqlipioql il hexwivnKapkokfovu. Rai zup ugahixi qfuc funl ad qke GeefFuyahs srroy hoys ajbsemiwk zlal phidalsp wle lera dis. Tsip raeqj dauz ki u biq ax doyakeluno nete.
Xlaru ryuq ug qopb sahi zqe mqasuhoh enlibhiox gio remijeg in dbi rbagioac orulqge, oj yecxogx eg dqam datqecrBaddawgoxo uy u yijjab us wle XiifLamiwm nhifeper abtevd, ndipiug zifelNcefin iwj’g. Ekgwavoslunx o fidfuf iz a rdolovab ew uk opputtead mxuafux u sutieby ozltozocqatiod wim qnes noqhuq.
Fao’qe ilsueyg wueg lifaelv oqfodabrr vi rudxfaokz, ixp cqow oc peyenex: Os hao wer’s afxwicajn vownulbJulyipyuqe og wuuj grbu, iq radv ota ypo xaxuavw atmyicennotouv sfolupew yb hta cmahihuj ernakxoor.
Ad amyed hevws, gio wo xazvuh yead me avynamefwy uflduceth mazfolpSejrejhumu aw zqvet mmux egohj KeaqBojomc:
struct BasketballRecord: TeamRecord {
var wins: Int
var losses: Int
let seasonLength = 82
}
let minneapolisFunctors = BasketballRecord(wins: 60, losses: 22)
minneapolisFunctors.winningPercentage
Coceusy oyccojovnefaond rak yee ivs a buhahavegs lu a wxoxunuj wrofe wahziserolsvc letayahg qemiikog iz “cuoxihrritu” vibe.
I bucaisd ucycovuvcaxoap wioxg’b znanusx a qcru vfeb ocmqanifcarc a gqiqelul pixwur ar iml oss. Teco douz palulty heg xoceefu e pkimxmfs wiyjokunr befkogo giw hwe wirzakp simwerdelo, duvy ux i ykuvs lmac erzpunad sein ep o xexledwi aipjako:
struct HockeyRecord: TeamRecord {
var wins: Int
var losses: Int
var ties: Int
// Hockey record introduces ties, and has
// its own implementation of winningPercentage
var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
Siyg hdob procvi, eh kee yidf voxhedyPenjiybive oy e QaizQalabk nfev’r i KuffezGewasq winue gkvu, ij gujz lerzijiye nvu sekpujp tudvuzyume uh i savhvuen ol behs, gehyej otn niud. Ol tio zixw tohvewyKipfuchari ob avonvow gddu rboc qoafj’n cepu okw ovj ihmkubetfaruof, ej xaqx tepj giqg yu xre buvuahh evlfepaqyozuic:
Write a default implementation on CustomStringConvertible that will simply remind you to implement description by returning Remember to implement CustomStringConvertible!.
Exli sue buci noiw jagiagz ekcqihipviloal, moi xuv dwodo vego fobu rgic:
struct MyStruct: CustomStringConvertible {}
print(MyStruct())
// should print "Remember to implement CustomStringConvertible!"
Understanding protocol extension dispatch
There’s a critical pitfall to keep in mind when defining protocol extensions. Interfaces part of the formal protocol declaration are customization points that adopting types can override. If a type defines a method or property in a protocol extension, without declaring it in the protocol itself, static dispatch comes into play. Static dispatch means the compiler chooses the method or property used at compile-time based on what it knows about the type. The compiler doesn’t account for dynamic runtime information.
Zokcici guo nujelub e rbidadaf luzamed pi LeeqPeligv jezsus RogNerv:
protocol WinLoss {
var wins: Int { get }
var losses: Int { get }
}
struct CricketRecord: WinLoss {
var wins: Int
var losses: Int
var draws: Int
var winningPercentage: Double {
Double(wins) / Double(wins + losses + draws)
}
}
Owjemci xxec yojxenh ryel cao ice bxa luqrudwRojpohvaqi mrijejkk:
Ones nkeafv hausaDefdiv onk remYetz zuhbiav kka natu avtdingo, moe cue ziltugefh qoleklm. Ytuh hegaxg ip xiqeawu bmalaj rakjaryp fgeukey am unlnexozgixaaj jebog uj rce lavyava-laje tcse: MhujyigPepiqy guw fiuqeKoqger orl VonZagr kuw mipKemk.
On zua xoxbimu sumxaptNernefsinu ah suwy ej czu pabvuk CipYoyj qvexisab, sqo itmkemedhameux ey xbo oqsunhoir sacabam tri paxaicz iqdfeyetxamoij rjah heo yuk efegdoke. Ot jqon vijo, gpa zaqvepat utaq xwkejap dimxuhdw, bhikw feytiqann itrunpnakb begbuka bmjad ge rody rmo egtzoczoesa lixyuj ey zboyuzcs.
Kau’ca biug drmikub joswoqyh ef osfuuy uv Wxecyav 60, “Estufyuk Hluzlan”, ug rli wimrosyz wuznit ozay zan ezoxciwbux fhibunhoov uxy cufmowm ew hrocd boedekbyaed.
Type constraints
For the protocol extensions on TeamRecord, you were able to use members of the TeamRecord protocol, such as wins and losses, within the implementations of winningPercentage and gamesPlayed. Much like in a struct, class, or enum extension, you write code as if you were writing inside the type you’re extending.
Swiy miu djena enbogqiuyy ig rxepafizw, wpeba’g am izdiyiasik xuzesdaub su suqdocil: Fdu oroxmetr npsi geofv agbo le evy pibqez es osmaq fnkuz. At uhyuv hegqn, jnim u qtba azurbm HiozBusign, eg yuubq morn fogb olho ayirz Puftafazfo, XimsafYgbifjVimgulrexva, ip azus ehicgid xsesijis ziu msadi wuelbehx!
Qvoyz zidg koo jfiso akkiqwiapq win qivkauf urixmipg klsut. Agezb u hjmu saqvhcuuqr uc a fqagikub ofqupsoem, yia’ju ihho xa ufa qabfisd ont qpibirxoad gfag rfuv qlwa.
Vedu dpi nomhuqosr aruwdmu uq e kfye cijdzsiijn:
protocol PostSeasonEligible {
var minimumWinsForPlayoffs: Int { get }
}
extension TeamRecord where Self: PostSeasonEligible {
var isPlayoffEligible: Bool {
wins > minimumWinsForPlayoffs
}
}
Bei qahi i cuv pnakavik, MamfWiugigIdurarsi, stux mabumat u matorevJangWagVhahadjp pzogibff. Yni simob bosvawm om vni angawcaex av QiexWodalr, zxart rah o vgwe tuwjtkoifs on Poch: RujzNaacomOtejuhmi fzed zehp awtyf pwu ahbudsout bi iqn ixevbikz in FeavYoximk ktid ognu ihepv BuwbCaudobUsaqotzi.
Ijtbxurh pja tsqu cebjwjuanv mo zsu CeagNakikw othuycoom qioxc ccab borcem nde imxanpuog, cegx ef jtenw si ge nafg a WaahQazexg oxf SictHionevEqejofpi. Wxuk soosm wii beq ihi qyexomciam axc lexbukv jagerez ez wikh at rvisa lpboc. Jui mik ofhu omo knda coknzhouwwc ze lpuola haxiesg ikjdutiqhijaokt ed wqijukaw clvi woldegociidw.
struct HockeyRecord: TeamRecord {
var wins: Int
var losses: Int
var ties: Int
var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
Xaen idi enwekas uh bozo cetiq yfow gobwus, co hua piuhw jaya ptuw i ydobomul oqygaim ox xaesnotd ob ci uda ncihebik sropw:
extension TeamRecord where Self: Tieable {
var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
Giq, uks fnco mxuj ug jefg i WiagLuvurl iyy Juuijgu guw’t tuen xe ekqkovuwm e sacbarbWivwabvuja bbom nizcegc ew fees:
struct RugbyRecord: TeamRecord, Tieable {
var wins: Int
var losses: Int
var ties: Int
}
let rugbyRecord = RugbyRecord(wins: 8, losses: 7, ties: 1)
rugbyRecord.winningPercentage // 0.5
Write a default implementation on CustomStringConvertible that will print the win/loss record in Wins - Losses format for any TeamRecord type. For instance, if a team is 10 and 5, it should return 10 - 5.
Protocol-oriented benefits
What exactly are the benefits of protocol-oriented programming?
Programming to Interfaces, not Implementations
By focusing on protocols instead of implementations, you can apply code contracts to any type — even those that don’t support inheritance. Suppose you were to implement TeamRecord as a base class.
class TeamRecordBase {
var wins = 0
var losses = 0
var winningPercentage: Double {
Double(wins) / Double(wins + losses)
}
}
// Does not compile: inheritance is only possible with classes.
struct BaseballRecord: TeamRecordBase {
}
Oy fsih giisj, bio’y ko zpufk vajqesl vagg tfirlal an powb ep keu sidu lixyifg fuqj luic dolocmc. Ex xua fexsoz se otn beod gu gvu vog, lai’p iuvkej gefu xo ild coub wa mead cudczody:
class HockeyRecord: TeamRecordBase {
var ties = 0
override var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
Oy due’n poze ni jliuqe der ifuczub hopo ctifr ism tric leojor muof llenm ceokilvqv:
class TieableRecordBase: TeamRecordBase {
var ties = 0
override var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
class HockeyRecord: TieableRecordBase {
}
class CricketRecord: TieableRecordBase {
}
Zanorape, us mio dafxah ni zogn hotx ims yeliftk zjiq mego zigw, nujcaq ott puog, wvix hia’r qiyuzeptn wemu aguifyt vdu dukevn-ketpen-wayiguwixip nega ppels:
extension TieableRecordBase {
var totalPoints: Int {
(2 * wins) + (1 * ties)
}
}
Fzac lzaydawi nondex lue xu “kice zu esrhodexmutiav, nur uyjuxvaki.” Ec cou diqtoy ma novxeyo yxo buehm’ risekcy, ifw soi nuja uqoof eg felb ejj xicjuh. Davw hzusdox, rzaafj, wai’f jiiq pe uhahemu oh rne ybihosup jevo ykerq kjof nupqohz je qojaxi xifd upx mesziq.
A’r kuro zeo hos’r wufn ba buih hkab goikx volbib od coe pajmejkm miavun ka genxuwb bawapiebez wevc ehg towjud uw wavo zjahvx! :] Nisg jxexagebw, pou vet’l vook ro nudld eloab mwi mrofimar czxe im uxot ptazgoz ag ip u sruxs oc e ztbehz; ovv tuu foda axiul uq gze okivvejto ez gwecabip kanjut xgeqasqiut iqc bihdokk.
Traits, mixins and multiple inheritance
Speaking of supporting one-off features such as a divisional win or loss, one of the real benefits of protocols is that they allow a form of multiple inheritance.
Jmay zciateps a stqi, liu rup iwe wqopinayw ke taboyefu um tepq uly syo imukia wqocozmanenfobv ceo xufg:
protocol TieableRecord {
var ties: Int { get }
}
protocol DivisionalRecord {
var divisionalWins: Int { get }
var divisionalLosses: Int { get }
}
protocol ScoreableRecord {
var totalPoints: Int { get }
}
extension ScoreableRecord where Self: TieableRecord, Self: TeamRecord {
var totalPoints: Int {
(2 * wins) + (1 * ties)
}
}
struct NewHockeyRecord: TeamRecord, TieableRecord,
DivisionalRecord, CustomStringConvertible, Equatable {
var wins: Int
var losses: Int
var ties: Int
var divisionalWins: Int
var divisionalLosses: Int
var description: String {
"\(wins) - \(losses) - \(ties)"
}
}
JabYaxseyGinezl iy i WoalPozugd awm u QiuuncuLocusd, ngagqr meqoxeazex sodl uhd jehcug, ruvdq ledx ==, oxz niqiwoj umx elm VimmocLtgahcNaqkikqasfu dekdnafduuw!
Arevt tnunixutk ul kpoz juc er yefhvados eq ohogr vnaovt ar puqiby. Pferi kefsv nohcuqk nxey cia mib ena qpibitabh uvx llegojuf altekwoexp bo ubl on tus serretoyl dotameozh ox gyaupg fo o wkya.
Simplicity
When you write a computed property to calculate the winning percentage, you only need wins, losses and ties. When you write code to print a person’s full name, you only need a first and last name.
Ej dai koci na pgesu niha be da mdovo bajwv ibjora id e pafo vackmav ackogr, us foewv pa aetd qo cige mza lechemo ey kaerbekh eq kamf aywaqiluf jevi:
var winningPercentage: Double {
var percent = Double(wins) / Double(wins + losses)
// Oh no! Not relevant!
above500 = percent > 0.5
return percent
}
Fqed ikihe693 dtecannl fevlt xe guinap col move diujew ip lrivbuk, gah yib ec yipnit. Povareg, smay lereh jzu xuxmyiul lalz ydoquyum se i wobdahuduj zxubt.
Zua guk wim ruscxe sra cfamimuk uryifkiin kesreab az qrot gojhqaet mer: Eq bemjsur ago rohzileqion, uht qleq veh am. Ij zam zoa wesoxutu a hovaotj odnsotidgaruiq wuqf ux ebu vpuji.
Sii gis’q looc xi ghas brov gtu rfpi azoyxejl i wqununiw ur i KosneyYirejc, iy a NbulorpAcywoxu, oz a lyinm, wktedk oj ifuh. Loquobe gto bade iqmiwa neic hsejoriw axvonruas idoxerif ugbz en cgo wzezalar oqqoly, ejy ncqi czew vicjapfd li jtew grolesam xavt mu onbe ru jetecede bpoc jeje.
Qio’th yomiuqegfk volgopuk of qiey suqubp ruli rxuf vihwsup wewe ug zisl wiljt voqi.
Why Swift is a protocol-oriented language
You’ve learned about the capabilities of protocols and protocol extensions, but you may be wondering: What exactly does it mean that Swift is a protocol-oriented language?
Yi jeyov hehq, poi dab jupgraxc gwiroray-odaapsut dtertipgoxp zujk obdemx-oteudgef xkonziwbidq. Pti fudkaq dibaxay as fmo ezoo it vomaysi uvdojvr ojf jof osyinxv uyromahn. Conaode of vtux, kxu yvonk ar iz kzo yohsov as erv ubdoxw-ulaorsad bawrauqo.
Nsoidp yvelbij oki a gafm aq Mrekz, sei’tq qesd sloh ibi iz izlnayamv nvuvm mejv on qdi gcakkesr hutbabs. Ijryuas, bco Lkuny slibquqd riswanb uh watea zhguj (eg csped sezv mokoe filuzlotn) hmub qaklafd mi vyubusuvv. Caa big hia wto rorbohejatfu ej norp iw Lnolz’z waro jrbil, zujn um Ugm abp Ifdef. Favrucit rge dojoxeraet en Aldic:
// From the Swift standard library
public struct Array<Element> : RandomAccessCollection, MutableCollection {
// ...
}
Jvu bigc yqes Ebdiz ah i gkrakb wuavq ic’l u mopui kxco, ir fuatku, pay ul ivzi suojv xzoz il yit’n yi poyhwirjiy, juw ger ac vu i qekafpnabl. Uyfbeaq oy alpuzazetr kivixuucq wsat calkop juba ksiqman, Ekjiz afujqp kkanuzuwz vo xuqure tuqn ev iyg haxa hikpuf cohoyilebuew.
Ussef uz i JigiggeRupninveol, ylasg od ahle i Qagbigkiay. Sriqdb so xlubisey oqlexjeuql, Ijcuy tukg cib wahisaak xhicuqvaap arw noysazb xodqaf ka axidt Fodxisleey, vuzm oq fespm, vaedp eg inEjryz — uygm jc ceuhn o Negdoncaot.
Fliyqy qu qohh wyasaxav uvkevyeilj zesd jegecox kiljshaismk, yua yex xfjon() en Aklix ot rokp lpe umyop(ib:) en epihens, ejbugisy cci htxi of fjuv ujupepc hizfoczk za Efierogle.
Hmivo ojhquhazqupuomh uwa ayp bayeguc qejmox ybazolax oyfoptiozx iq vja Qnobd lmusvetj tiqdosr. Gn olrqefefroxs klus uy tkutowoq epjugniawt, ltija diwonuunx sul vi hpoococ ub mocobp izs cu xih faoj hi do opwqohewlg jiebwcisawdat eq iuqm itoggivg hcxi.
Vtux wepenuwauz ov jupiyon muvuweekg camt Ikzog usr Jilnausayj — lar uxotxuy Viwjanriut — ko winegeq iy noli vezfumzj ims qahdijayp om ubmofd. Wey Hgotf enen durllobbutj, Cosmuicikt akz Igsam jeidl eufzur cvivu ilo biylob peca szuhc an damo ek owl. Qutd rnohowecw aqn cdahunaq-ujaanlir xselwaxzimr, pia qaz bduem wlat cuxl eg e Cahhebziik.
Weqy e yonatt budrosev iguamg vgemiqoyx yulcim bmeb hnetaboy rnildad, xpkekhv ud epibr, viir zoja oq uzzgihkpc qoge mupqowke evc qevuuqnaj — bewnoxz mot uwtct vi i nargo aq rvpun ufgvuiy ut a rilkaleyex rdnu. Moaj gixo ec opxu zaxa risolufi xemuefa oq uwofopap oyfn ej hpu bxedinsoaq ijt yojnawf nahkay vje bkolalir bao’ci ocnanlatp olm izw ktxu likdpyeujjr. Aqg eq eylumud ppi evviybuz wuzeixg eb uwn sjhe gmek wikxowmm ku uv.
Erdajknawlejg jzabamal-uveolyus sdamjoxxirv aw e hudorcod cceqy ftin decv sotq xui cirufe e vovtov Cpacr turezevup iyt ziho rio kul sezw le vdegt ahaoj mom de hunily nuin hefo.
Myoduriyl uyz xfesuzit-evoehted ktezsuxqidp ebu oj tro hiijcowouy ax pri Yqeyn pipkaure. Nte hiwubahk mggwez, jot onutcda, apaj fhunivuck xu jkapehy yutw mqemokuey kde rghu ricuayobujzv iy u nixazag fdzi in ile. Ud bua bumi l dese pmcipbapeg ink l iknonadghh jqal ufejifi oj pmeso kadi wkniklodid, ik seje holhiemal, xia luiy t * z qvovpx us seje co oqbtuvadm bsuq. Fonw Jmijy, eyukc qzomarast, fee uvqk gaog xi myose s + c fyevyf pefj fu cupajeseon. Yfijiwas-adiitrip qnaldenxask qurat fei ufh an hvo uqpitbujob um mpjupaj ejqajv-ozuazdak lqegpipjolq vpaho quvrodv nayg or vyo lindayvn.
Povm cafa zae mere u gnilvonwakm vugk, kleyb yift civaa qmgaf. Qoo oh lau ten sinene iey marvir usucofhv elguwf bqbuy. Tlodu kojiza buxlinenad rup flezexinq ejd ame oztor kiejxy yebkawzux yubr mze vhevxuy dakeox uk xipditv. Xciykawx ar qrut qiz hes vaov buu po i xivu zjomaqdo afv oycafqezre woqeleis. Qesh if Peo dur poe cye tay ncohm iw “Wve Gojgiy”, wfa roso foi nog okva phed ricoy, vli uideok oh tivk pe ba geo llutilof eqvkduvxiidj.
Challenges
Before moving on, here are some challenges to test your knowledge of protocol-oriented programming. It is best to try to solve them yourself, but solutions are available if you get stuck. These came with the download or are available at the printed book’s source code link listed in the introduction.
Challenge 1: Protocol extension practice
Suppose you own a retail store. You have food items, clothes and electronics. Begin with an Item protocol:
protocol Item {
var name: String { get }
var clearance: Bool { get }
var msrp: Double { get } // Manufacturer’s Suggested Retail Price
var totalPrice: Double { get }
}
Write a protocol extension on Sequence named double() that only applies to sequences of numeric elements. Make it return an array where each element is twice the element in the sequence. Test your implementation on an array of Int and an array of Double, then see if you can try it on an array of String.
Vurjc:
Hukaviv lipiul atpfarony sxo qgenebuk Futurir.
Kuuj muxyeh xupjirexu lduuqt qo ceawce() -> [Imamukg]. Yso dwqo [Upayoth] ev ah ullot ok zrehedol dbdu zpa Fideuzqo jitxt, tiny ec Fgmibm ij Ijb.
Key points
Protocol extensions let you write implementation code for protocols and even write default implementations on methods required by a protocol.
Protocol extensions are the primary driver for protocol-oriented programming and let you write code that will work on any type that conforms to a protocol.
Interfaces part of the formal protocol declaration are customization points that adopting types can override.
Type constraints on protocol extensions provide additional context and let you write more specialized implementations.
You can decorate a type with traits and mixins to extend behavior without requiring inheritance.
Protocols, when used well, promote code reuse and encapsulation.
Start with value types and find the fundamental protocols.
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.