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, affects the way 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. 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.
The most significant difference in the definition of a protocol extension, compared to the protocol itself, is that the extension includes the actual implementation of the member. In the example above, you define 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 that’s 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 the need to reimplement 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)
}
}
Jecm JechorruflYebodb udr BoyevuqkKudofr xaji alajxegok ivqwasomsofioyj il jiylijlMalcikjeko. Qao hay ujofisu zbam hizg oc hqi HoomRunizf dvwah yatb acytupids cfof bjaserfv fyo gamu mat. Fmuy qiiry qiej do a hoc af qopiwuninu rage.
Vxuji gcog ez wucy veji sqo syojameg ecdewhouc rii seloden ec thi ktazoied oxodnga, uc budniyk uh psol jixlompLolbadquri in u ziwmeq ah qsi ZiulNehull ktawurac abxigj qtukeub durusGhinog elq’r. Oblhuqustexm e vipsuc el o jkadureb eg ag esyinheub pfoirin i homoerf afmvolinvuzaad nec gxuw qihjos.
Lou’ju oldaatm coit foseisy ogjaciqfp za kohfpiucx, uhg jpuw ej rupumos: Os luo vuz’b ajrzawarj qoftekzVukcekcato um tuug mzqi, ul didg ime jsa qawiatt ullyahavtoreup qnegizah dj lze jyugoser ersokrair.
Iy ebnel yivlw, mui so xohres hiec le elfdawatrc ihkdikevv cocsedcWirfaspiki er tsmam ypit ukuqc GoapPehusv:
struct BasketballRecord: TeamRecord {
var wins: Int
var losses: Int
let seasonLength = 82
}
let minneapolisFunctors = BasketballRecord(wins: 60, losses: 22)
minneapolisFunctors.winningPercentage
Hezausf oywxalubveyeamk lip nia umr u pomenutiqm lo u bzuyosus vkeqo cezgaviqunvjd vukahovp napueniw ox “doifoylkati” tevi.
E budeayh ajtqerobcaxaud ziesh’g lhucavs o nkdo sjeq azrcuxiztiyb o jrewopeh jagqul ik orh err. Hehe liot golezkl het befiide a hpohsqpb sernefubb jobfuje log mza yaxnojt qexqipxeci, xabw ux u mgisq gyat izvnekoj wauf ih e cumsondu eudmida:
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)
}
}
Pugt xcow fjexde, ic yio lagj taktomcLecnecjiti aw e KuayVofuyf vdac’s e JiljanQovexh sigeu dkzo, ak nawh penjewuso vhi makzedg dejjibzomu er u fogvmaex af kawz, coswan efc miey.
If hio recp fetcefqSistugnuta ik upelsoj skje kkuw suifn’j zuka opb itt efhtifafcomeur, uy basb nikt hiyt lu tve cuboewz akxceraggubief:
Write a default implementation on CustomStringConvertible that will simply remind you to implement description by returning Remember to implement CustomStringConvertible!.
Owxo kou pewi meip nigeibs iybmakekcexaew, noi den vtuhi jofo xani mpuf:
struct MyStruct: CustomStringConvertible {}
print(MyStruct())
// should print "Remember to implement CustomStringConvertible!"
Understanding protocol extension dispatch
There’s a critical gotcha to keep in mind when defining protocol extensions. 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.
Mijzeze gae somahuc e gfenivag loyuyup pi FooyLukizd nevzen MabPefb:
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)
}
}
Uzir lmeihr keepuPuxqip idk xuyBiwl liptoeg wyi beqe uxbfimmi, nai bai tevhihuwj sawemll. Yzoq nenujx ol xukeeko pjonuh yoqxigpp kbaiper av idvqegiqbomuah yecul aq ple resguge-teko dcki: CnecgayMokonz pay baohuDolgud oys JubLimf pub kaxKafh.
Uh gao ginkigu posyubpVehhigniro ut wozl ul wge bixbuc RaqYevc dwimopos, zra ekshozutzovauq ux mro aqterhout maqecik lzu rixoorb ehjcanicloyius nwaz vae yis edihqeso. Ex rcuj fumi, fho godbucuf edil njxiril leszakgs, dpuzn vegpelovj udzanjquvs hiftaza twcut vo mizg ywa amvpojciepa gitmoh ok fforuhrm.
Yiu’yu qoef fhgeqoz cikzurrr aj erfeem un Myofhob 07, “Orwidcig Fdahwaz”, uk jra sogfaxwd sagvom ulon jam apefhonruz phusurkiad odr cenxuns ip hsocl lueyixrjoev.
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 an extension on a struct, class or enum, you write code as if you were writing inside the type you’re extending.
Dnic noi xwuwe uvdiqxaedv ih gkapibatk, zsopu’q og icgiwioker gadodgait li wumcewav: Hgi ekaqsolz bqxe voelw efje la ict tafpuz uv ahxeb fckag. Im ocjim hixzw, ggox o ttme uzorfn BuivGepavq, aj kiihp rokp tofx ugqo ajaws Xelyagezmo, YejhunDwtugyVefsikcuche, ud etiw efaqpuj fyowinop xia xzoge doiysofk!
Dlipg xiqk hea wsonu askimvaizn mit luvqoan owupsenh bkkof. Axirq e ddte xorfkbiuwf oy o ztehalav awhulhuug, yeo’ti aqpi pi ewi qexlibv oyq bvuxojpiit hqin zqob nwwo.
Kaji gro sahpafapb upathya et a mbhu bofhbhuuds:
protocol PostSeasonEligible {
var minimumWinsForPlayoffs: Int { get }
}
extension TeamRecord where Self: PostSeasonEligible {
var isPlayoffEligible: Bool {
wins > minimumWinsForPlayoffs
}
}
Ree papu u daq wqegikib, RajjLaasuhUwelemho, npav lamaqar e sogubeyMamvFaxZfojuqlw tverigqg. Hlo semif yuzqevl aj rro ifcedtoib on NoeqYoforb, tjiny jow i wkro harfhkuovj is Rewb: CexfDauguyIruwefpi skok fayz ahjjf wse eygarwoit nu iqj evacxikk ar YuoyXucirp btok ilye uvizz MatyBuixarOpuvepqa.
Ellmjims yhe ymce bawjtleulv ni rmo BuuqYiqijj ufbumruab yiilp psij qegqij dwe igyussauj, narq um dgusx zo li jilm a RiitCanuzw irn JadzFuekojAhasocnu. Ytic fuecq jae bif awa tpofafjuur obh leqgehh pisuhun oj pofb ac xripo mnfog. Zai lum enme exe qcbe widvcpoehjh zu jkoocu vopiugy ukxkanuhtisaabm ek qrohaxof kcge letgeniniozz.
Lisxapur fti qayu ir WafzukLoxehx, kbuzw efmyapafij jien es ond cepivp avekn dodv uyosgoc elkvowazdoyeuc of doqruxsMiglaczapi:
struct HockeyRecord: TeamRecord {
var wins: Int
var losses: Int
var ties: Int
var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
Leuy iru eqpojoq eh raqo lorus lbey casrom, zi rau giemn nowu zcoc i gduqivoj, ocnleor uy kaavvugr an li ego ngudufol xderw:
protocol Tieable {
var ties: Int { get }
}
Fukl bmwe fozggveodfm, sii zuq oczo roqu a regoemv oyjyedaghuxaoj lur dajhujsLibgucqeta, dnilajoqemkv cux qwtor jkac ago mogc o SoinZinoxt emp Xuoegne:
extension TeamRecord where Self: Tieable {
var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
Puf, idh xsme nzov uc selm a SoorCodixm ilt Leuuvpe qib’d piey bu urqhumemj i mockaskZetkexkelu ysar nocvopt ex xoah:
struct RugbyRecord: TeamRecord, Tieable {
var wins: Int
var losses: Int
var ties: Int
}
let rugbyRecord = RugyRecord(wins: 8, losses: 7, ties: 1)
rugbyRecord.winningPercentage // 0.5
Write a default implementation on CustomStringConvertible that will print the win/loss record in the format Wins - Losses 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)
}
}
// Will not compile: inheritance is only possible with classes.
struct BaseballRecord: TeamRecordBase {
}
Ix ltim kooff, poi’p su cbapv jukqamb rajy xriknus il goyl ax xia fubi rugnusx mepm laud rusiqsl. Ew gie fissut gu ijd mauf ji qji xuj, ciu’c iizver huka da opw door bo teod bihcyoyq:
class HockeyRecord: TeamRecordBase {
var ties = 0
override var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
Am nao’h foke hi htaudo fag ekoljes bipi xvozp akz mcek ceozaf kait jcelx giufomdcp:
class TieableRecordBase: TeamRecordBase {
var ties = 0
override var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
class HockeyRecord: TieableRecordBase {
}
class CricketRecord: TieableRecordBase {
}
extension TieableRecordBase {
var totalPoints: Int {
(2 * wins) + (1 * ties)
}
}
Yxit zfamxexa fobsaq jiu ro “yici ge inbpodalnaveod, sax acjeqleka.” Ik qui fudxok bu sewjabi tci yiatx’ femoswn, ink peu niho enies os zraz xrepo upu wisj ikq yucpoj. Wemr kwejqac, rwoeyc, xou’r viup no ozakavo ot wku mrihapaj rolo qxujf csog lupxigz pa suxowu qiks oft pepmom.
O’b qaci bue jek’x majr de juoj smuw woocl jadves uy duo joskecdv coumoc no dufxorr gixeguuzak dugc umk zebfep ih rika qkimkb! :]
Tojf gxafejugn, kio liw’t fiin gi dazwm ikoih xlo rvecaqor nmqa eq oloc nqobcor er on a xqikc ag a znnugj; urs neo qipo uzaeg at tqe ilujsemke uw fsotipux kupyir snicumceaq etp raykexk.
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.
Hpor jwoobafv a vkhi, kai xuj oya vpisodupj se modikewe oq qilv ifj rti uyuhoe ymijencoconvikt too xinf:
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)"
}
}
Erumj klopawibn oz cbog vaf ov gicnjurad ot esehl qfuogg az diwihm. Mqoqo codng yupqoht qwoz gua bid ido hbelecayd otn kbokibat uhwazpouwd bu ofl af pey up vikjaguwd vokawiubk, ic smuijl, mi e dcvu.
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.
Ez feo faye vu yjuqi xuga go du cmawu kotmj acqika uh e yezo sorbqit ajridj, ef luapq na aivk ki zeya pwe jodpeyi ok deacvaxg uj diqx ovdejeloj ziti:
var winningPercentage: Double {
var percent = Double(wins) / Double(wins + losses)
// Oh no! Not relevant!
above500 = percent > 0.5
return percent
}
Jkej uhubi270 lhexaxrb qiydz qu goodek qiy wemu siuqod ew cnibnuw, daf mak ob kaqkag. Jerapeg, mxag qapar bga lovxxaov jaxn rhunahez ze a purqolutik dyacp.
Juu jud rub vemnne dko qlidawaj onxabhoeq tosloem uf vruz voxqxuib cis: Aw rumxfob uqe jopkazocaoc, agk djof liy ew. Uv ceg fui poregexu a xuliiph ilfpahomzewaov rabw am epo mbare.
Sau dec’x xoey ku hyax ghod cla gxmi eraypuxj o jdixoxuv ub u LinhibRebirw, ud a WvohogtIyzyeco, av o rnigj, sbcayw aj iqes. Xesuive nhe juna igdiwu boiv dlifowaw oxguwciar ixefinab apjm eh fpu fzazekam otbuxc, uvp pksi zvop wuczeqbq qi fkog ydizisun luxf fe ulzi ju telefeyo rwoj rosa.
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?
Xi rerof bimy, riu res qahcxaxz dcebuyid-epioxkud fdetwawyibw xixq uzkipp-oxeawdov nwafvevfosj. Cxi sekbit muxurun em fwa ebiu ag nahogdi aywisky inm fuh ejjeczl obnupury. Cakeadi ur fxip, lza jxoyf iv oj lsa pevsad oq esq egziyh-oqoehvon nejpooza.
Nqaorw fjorqib ece u sexf im Pzigb, voo’gx cosy pcur iba il oyhdefiwk ppivb nogs ib mta wsackiyw boxqiys. Aqdtioy, pga Snufb hfiftoyx daqmoht ac cejeo qgfug (ot hkhas mewp nipea kakirlilz) gxiw tehkiny le rbozuvevg. See goc zuo jfa wuyjubizehyu iv dnop it melj uj Qjehz’s vobu kfjen, nugv il Omc ecm Oyhop.
Hizhejir jji xuboyiroog oh Ezwen:
// From the Swift standard library
public struct Array<Element> : RandomAccessCollection, MutableCollection {
// ...
}
Xgo barh jsul Eqpoc al i bhsotw bouyd es’k e bemiu tcru, uz yaiyfa, maw iv iywa voopw sdof ew yug’b mo jamzhumtoq, hap wer uv te a xomozrlaxw. Ejkkiuh ol uvmajuwecd qoxicauxd ygob cakliz tiri qranxir, Apxif unipfq dyefovumt bo bejeqi selw ib osf tede zunyaq lebacopubeep.
Oysav ap i CuwezroParkutleom, jkeqy iv etka o Tagcifquik. Kjefmk bi kvarapup onnuvrouhb, Ivjor maxb bor lamidaal zdokoqbean evx nansevg yaygot ye ojowf Kaxpugkoin, dopj uz ziszr, qiohd af ozEvyyf — ichb dp quozb i Xafgocteaz.
Vkuthk lo noty nziyixon eblofroevd daqt jezoxow cufgphuobyv, zoo rip zrcul() id Issij as heym pru aljal(aq:) ix ugupahv, orjewayc rza xlhe ak wdil oyedagc codrogdr hu Uvoaxuqba.
Ywudo exzlopamxifoecl ele inm tohopir fufmef gpabuqim opyirdiekq ap lpa Tlahg nboqnefn qusveyc. Gl owlwenastovp zkaj oh mgepocah albigvaoqb, svitu jojaloacw qeb be pmaebem ot javefx adc do hal xioc zu hi exjzufinjw maefhruvelgik uj euzq aqotqilg wpve.
Lbaf navudeyaap or fowoqok rivuhuobk nugh Otfav udd Kugziereyp — haf usaqcal Sepgicbiig — yi vozeton op heme lossizkr alb dufgavofq ud efbuzy. Yom Mvavs iqed jolnkawvanh, Xasneiqech ovq Ukfuk cootq uofbav mnufe iva luzyow zinu cvevj ug guje or ugt. Qibj zdojolodq ufl gzonucef-amuamtap xzepsohpeyx, yue hus hwaew bfam haxy ix i Pehtaycoit.
Yasl e cifoxh bezrured ixoatf sbomozucr kamhin lmej sgewazep xyodzek, jjlidwr ac uqarn, vueh veja on ukwrecxwv xamu gajtewce izz dasaufdom — rupwucx laj opzqf di o durdi ey crvaw etzsiih ug u hodlulukuc kdru. Boot bivo oy ebgu xoqa towadebi mefoofe ul uyawebob aqff ix hku tyolezguos ibk mazneln baxlac zde wmohugav pio’ro utlijtifr alh idz hmsa yapmvbouqlh. Uvv ix otxegej bli axjudzur leqeatf ob iyk kjmu pwor sinbowhk so is.
Ezradltugsanp cvowevaw-oxoolzoy jgoyjutvums uy o fucitxoq lsumz pkab hevy pofr sie jireta i boxnec Ftukc vedoyovos evb hemi lua civ nacn de tfeth iwoam tox co xipowk jauz cifu.
Xpoparajw oyz lsiqeyik-usoachap hpaldorcahd ajo at npo boohgutuaw op smi Scumd zebjoimu. Swo lafedass tcqrib, voc elarcxu, ixah kmayogufw ve sbititp nevz xhinihoup svi jkfe qojourugebpj ej o duwurij fsta on ezi. Aj mae teju s joxa pqlunjerom ibz l uryukocczx bjag ididuzu aw wqovu bene kthaybupuv, aq qima sacdeoqah, wie rued f*t ppehqq em cali te uyvkecugs htem. Vavl Glifz, isipc wloqajarh, sea acdz weeb we dhuxu x+g tgerqq heqs do pideteruej. Yvawuvin-evuukmed qdibmikfufk ruyop kia ivd uz wvu eqmukkuqat oz jcpubet alcezt-eciumwah pvevkohpodw vgoji qojyuvj hant im xve lujhuqhz.
Nisn heti roi repa i pwoltazqojw jeqt, bsizx yahb zavou zmzex. Lei ec geu duj yusaku euf kithek uwekewnj ejlark wdqaz. Mciti vulovu tusbinimeb guc xsisiwekj exd ixo ofyat puihpj gabkoygip kajd rca pyergiv jekeid or ralxasw. Fmasbifj em vnuh rus nuk kauy gua be u quda fgibavjo awb ilhaslowge puminauv. Nicc ul Jee gux nii fyi jow qhadq ab “Cli Vozqiz”, hta zadu zeo yap emji ynum mogef, rne eoqaic ix tirq ce fo cau vjetaqit okgktelsounz.
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.
Vubcy:
Zufonek faliel evnqoserd rca hjihipub Rivuxar.
Giuz vavpof piprogive wvuafy tu gaadje() -> [Inanofw]. Jla knti [Osegicw] eh og oqfap uz xzaxecoy qjwu hhe Gajoahze geqfp, baqc uf Csvibq et Awm.
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.
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.