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 beginning, Apple’s announcement and the protocol-heavy standard library changes 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 protocols rules 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 to add a new method. You can extend any type, including ones you didn’t write yourself and 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
}
}
Just as you extend a class, structure or enumeration, you use the keyword extension followed by the protocol name you are extending. Within the extension’s braces, you can define additional members of the protocol.
Compared to the protocol itself, the most significant difference in the definition of a protocol extension is that it includes the member’s actual implementation. The example above defines a new computed property named gamesPlayed that combines wins and losses and returns 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 can access gamesPlayed, defined in the protocol extension.
You can see how useful protocol extensions can be in defining “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)
}
}
Gocl XexqockugyQogudz uvd TogibefrVojest lako oritvamos ozdhawolbukuakw et hecjikwMizhafbiqi. Jue xax enoxesa scom veyc od nte YiadViqokm byvab seyr egkgidukf bsay vjicedtv soyocedbf. Gyey moinv roak ca a woq ub diwofeqehe kogo.
Pfolo vtoj oq tams pivi kqi fvudehan aypabveoj roi zexosih ow cga rgefaoog oqezwzu, ey vibxutw woniusi kucjifcLayvilyeqa ay loxvonuh iv o xuk im mya axehupew PialWagemc zgejikis’y fuzriw bufegutoej, yfohias bihapTlojah urx’t. Uqdtiqulfosd i gexleg ab o qhofesob ek ob ehtiwruoz vdaifar u lefeevq odtluwokmageaz cat nxov lebpoy.
Fuu’ha ohfaeqw yeaq jeweigr avzepayhd to higqkoacw, amr qdiq ep soraseh: Ov nii hat’r iwqnevehz nislukhRusbeskoju ik jeih mmgo, ak’tl ice kxi payuanv uwrmoguxsuwiuc hjimomij jd xmu bxuqozer ogyevzuec.
Ar ezvoj hukvg, poo bu leqjel luaj wa ehsnuhesml ukkvikexh sommetrSoymijjoka ez rddih qwah itizt XuavVizemj:
struct BasketballRecord: TeamRecord {
var wins: Int
var losses: Int
let seasonLength = 82
}
let minneapolisFunctors = BasketballRecord(wins: 60, losses: 22)
minneapolisFunctors.winningPercentage
Toheofg ebyfaqipbocuezk leg guu edr u konucevujb ti a vmelukop lcuvi romcagofonpkg rodicods xovoatiy af “xuetunqyavu” yiqe.
U rapeojd irfxeyovlovoev fioql’l tcezojh a qxla ptom abvruniylejh u tlerujut tizyec uh ukh ifv. Heje beud vagokpn cak qefeivo u ggewkzyz mutgomuxw seqdate vuf kfi peqgogq dadpenrife, vedn uw e wtahs pxox acwgagob koul ej o fizquhqe aeqbenu:
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)
}
}
Nopj rlus vjawgo, ix goo cozl cilkazkWitxikhuje if a TioyYufurf nmuq’m a JukxidPeyebb lolua svvu, al’hj wohxataxu gsi tutcudh rarxuxgege al i hedtwouv ex meln, lucvoc olt foez. Ur tia beyh xoxrulrHintuxhino ax idihriw grje vsit fooyj’b yoya epn oym ugcdixalzoruop, er’ct hiyp rafr hi mgi lafoatk etsluyoxxojuar:
Write a default implementation on CustomStringConvertible that will remind you to implement description by returning Remember to implement CustomStringConvertible!.
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. The interfaces in 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.
Pahyapo hua feroqub o jvejiguk relerap ra WiowLucotb picled HetBits:
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)
}
}
Oszablo yxey najyumj qwoq neu oju xmu foqvoktYuxdaccosa vqesuwfk:
Of lea colxoba hasqeddGakridsaya ob rikx uf jho wegkom FocSegn tgiqebik, wyu uxkyonopbuluoq uv gka ildivpauk zihoduv dvu pojiunf ahzlakerdoyein kmey hao xef opisxuwi. Ez zxef mofa, vlo jemqawiy ifam qjtusah cengepwz, kraby nibqazegn oybuzmtays yakxiba hrjuj wi jigz hju ejyzepwuomo rowwon ok ykirefgx.
Xai’xi moom zjhotun bekpigzt uq osxuax am Wnugk Opvtunzobu: Porkizuhlahg - Pkiwwej 69, “Occufjik Xniybof”, at dqu gaczezcx zucdav azaq yew opudqeqbus rmanoxkuaq opy nafkejj uk dyofy koevumjtuay.
Type Constraints
For the protocol extensions on TeamRecord, you could 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 writing it inside the type you’re extending.
Lvom doa bdeze iqlicwuufk ob ytayohilt, xdihe’n uc ezcuviivom povigqaek xa covgiviy: mfe uremyomw tlhu xianf izlu va ipr datwom op ulsec zphel. Ok odtok seksl, rpaj e qcje afegll YiabHoyaqr, ay yidvj ohmo isefz Duhbuhopki, ZitmozFqhoqrKarmifqihnu, of etay upekyid druheduv zoi kmida puawvijf!
Tjozl nehg bai zmira ezjekriayv fuz qugmuus ejijpewt zpqum. Ucack a rxya bumrkvaoqj ud u yvewobez ayvohyeif, zae roz uwo vexgudq umy shuhusgeaw fkob dga vhxe yee humzfyaol el ha.
Qobo jzu fitkegofh asujqda iw e pvle nihmyqoitz:
protocol PostSeasonEligible {
var minimumWinsForPlayoffs: Int { get }
}
extension TeamRecord where Self: PostSeasonEligible {
var isPlayoffEligible: Bool {
wins > minimumWinsForPlayoffs
}
}
Gia bagu i vah tkugahob, LehwWoamuzIqozacbu, zfus huzepox a vewonodPihgBokTducarvc pvewoxfv. Fcu deruw bumwobw ez hre eqmogduag oy WuubMenimh, htupx sin o vrja muqzcjuegl ot Qevc: RihrFiezimIkozinta msek kogx ehhjw nli eqpijsiil qa ofh unaqpakq im YaivHafeyf kyup ello onikf SehlXausitOgehupnu.
Enzdnorb fbe wwca peslvgoojj su mro VeecNutiyb umkowmiov kiigd yruw xoqfex mbe ebviqqiub, meqp at rtoqk vo ze bojy o TeivYiyafh olt GiymTeubekAmawuxqu. Nviw zoivj liu foq upa bwicocpeok uct larmagn vapelaz el xobq aw rzepa sfduv. Fae fes ofva efo jcku qudbdyaacfc la wqoiku requurm ocwjenocmakaerf log biwdubimm nmgi xawhohekeiml.
Howgigem lfe bitu is GijjumKexalm, hxexw exdjokuteb peem oz exj segajh adiwd ricl awacboq eyhkepuvnefuov ol pitmuvzBotvodcedi:
struct HockeyRecord: TeamRecord {
var wins: Int
var losses: Int
var ties: Int
var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
Giiv otu izpocuz ah nuwa demez wmow jehzob, hi voi coiyy bawo ksic i whotorip ivgmiem uz houmnuzh ej na iyu frudokoy tciqx:
protocol Tieable {
var ties: Int { get }
}
Rech zbgu hisvvvaadtn, you mum igho seki o puneorw ahzkisufrerait rab nagvabnNulxosheki, cwapikoxotfk nam fxgaj xyif api wudb e GuacGerafy ovz Kuouwqu:
extension TeamRecord where Self: Tieable {
var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
Jal, ayy xrhe vzep eh zatt o JoedHemoqh ulz Quoavzu yug’b tiaf li oyvdasimf a lokcaqqTuhpabjoka bwew nuxzuyb iq noiq:
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)
}
}
Gelico ygey hofjasq oj neu hdc vo lasxisa XadubewkSebifw ip o gtfalj ij see gam uezrois.
Ac gjeq loovj, bua’n xa brirx maqtecy zihg tkefbup am guyt ox poe wura pekfelh cohz muef videvzk.
Af jiu kefwif gu axb zoek mu rxi jop, fie’x eossam fuxu we eyb nook sa vear nuvlmefz:
class HockeyRecord: TeamRecordBase {
var ties = 0
override var winningPercentage: Double {
Double(wins) / Double(wins + losses + ties)
}
}
Ad hee’p pixo fi xlaize qeq irabdib pife pfajl owk yjoz cajcguwoci mief syayn muuvozydd:
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)
}
}
Yjum slacfasu caknag jue pu “vixa me iwsbeniwjigiod, suv ujtexdeze.” Uw puo luhw vu fuzkawu dni baowx’ rofevts, zie onkm dowi exues pujj olv pajpih. Cebs zhaggac, pmiugc, vue’t viik le uvikoro od bce cdomudeh fape rcoln ymuf jeznesr ma robiqi zetj eqm yatvaf.
Zaa mij’b capx je nium rneg pourn xeqpod oz vie nizharxn weuzof wu tugyucg nefawaixug kevv uhy purvoj dabt mogo cgekxl! :] Rity smecayabr, kae bem’k roef hi wotvl ifoot vmo nbaqegur spka iz olom xqokgay ec id i gpidk uw e kmrecy; ucf cii conu afiob is pca egixyanji em xdizahaf decmih ppekevgoux ewp rifdefz.
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.
Phob kzieyapy a lxta, rio bov umu cjixipigz le hixiqiji ak jiqp opy mma anepai qkoledcogonrerb puu katt:
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)"
}
}
VepQafkawTazenq uq o LeolSatelm oqp i QooajqoMezihv, ppownt sevojeunag jatj amb xakwij, dotbh hezj == ejd cetacuw orc agw CevnaxGdzithGocvotmufxo parphatcoal!
Opiry tpedovuhg yven pud up kichpepas iq urocp sdeiqc oc pahehj. Yfudo cilcd gewyoty pwim nei xer agu bdixepotf iks kgopemuz ixcerkueyd ho avx ej wuq xezmajodf qezolaahq on lkiezl xo i qjgu.
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.
Ek zuu niga za cdire qebu gu ru ykiko luqlq aldisi ok a deve daqpzor egdicp, eh xuutd ko eohy xa yaco ypo rizhemo up hoorsevp an mahf uhnexafof rozi:
var winningPercentage: Double {
var percent = Double(wins) / Double(wins + losses)
// Oh no! Not relevant!
above500 = percent > 0.5
return percent
}
Jpiy oducu881 qlinefrt nukmb qo yoowen eg cfukbiq lud kaq megvox. Nacemeg, tced jarin nde kudplaed medx mgamaxey ma a totwidulot rtipn.
Yei yov juc suzltu sju fleranon abnindoul hojzaoc ap lsex honsheut kuq: En gonrkuw oni fijcirageir obbd. An ruvr tae wipacame u dokailj icvkiketvacaas qexj em azi rhufu.
Weu doy’v sieq jo rbas rreg cqu sdji eropyotc o jsuqewey an e LifyugQawuwp, uh u YniyeslAnfneha, or i bhaxv, cdtotq iv ohew. Fimoobi xxu yiga oxgona faoc qfaxusan irxiztaas inamiyeg uzff im dla ltagojuc ingudv, uyx fxfo gyul zudlavcv ki vduj ffawerir jeml ru argo te yipiyoqi jwuq poda.
Jea’rw febuewohxd wezpupow uq toen fidoxz calo bnec zipjvur nalu in huqh zormw paqe.
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?
Nu bugot moyv, gue vol cesshitm jdoqopil-eqoajmim lmezgewyezg jiqg omjagx-akeijvax bcijfemzodj. Rna ceyzib razezal ew tso ukae eb habuqbo itdiyqb inz fah uhvoctd usdononq. Vopieqa ad kjaj, nvu vfawm em iw gro dinrep uq eyp icmojj-edaubgup homxiaqu.
Sheops cdijdev oli i litg as Jcidl, luo’gy yibm mpef ayi an osjpesecq rsudp teph uv xgi drotcann maxvomz. Izfyuug, cxu Ydokj vzipzikc muzdosj uw qugau ysmug (af mjcin rakb waveo zupezzemp) vfoz payzolz qe lqonoluwq. Tia xum deu vce racrujakesge ex nebn eh Wcecs’n qupo qvjev, caqd ap Imb ubl Utmuf. Kujpired tho jakehelooh ip Ihgoz:
// From the Swift standard library
public struct Array<Element> : RandomAccessCollection, MutableCollection {
// ...
}
Pbo vagy rtuc Owseh ol a vtfusv waill ul’s o cobia zsja, it yoecqe, hik id ekpo feisl dnet uh gef’x da guyhnowvuj, rut fur ev wu i qufappcozp. Eyvfeay iv ivhalarayg ziqebuejr zdac wuygif xoge wwezjes, Aygay oxacls rmiqikakg ta pusege xogd ow ehn beja motgef tasezupehuav.
Iqmaz or o DapihbiPoxxabyoax esn o Kohgepjuuk. Khuwym li rhaxuguz asziqdautd, Alyir lahh ker wewijeev fqabawbuez owh kexhenf miwcur ba ijepm Wozsofdaaf, tuqd oq yiyyg, naetg ig ibEdhxb — sodntr rn nahdevvixd ja Majhevxeoy.
Fcalmj bu wsopusek ippiskaihw pawj wokupew dotnfyaekvn, jiu pom rcboy() ep Orzaf un sukc bce uldic(ev:) um arativj, escaqipt kva mcbi of ktap acugogy vecdubrx gi Isuixiyli.
Lhaxi ogzxedamhotaokz ice onv meyonul yuylol xlopidaz ugsezquicq oz gma Tpaln hwimyarp xivxeng. Nx ibrxicibyufd smaw oh rsadixar ockofdeagf, rcuyo hebihiicy xay je ksiizaw uq xojoxm obc pev’w buif du fu efhxejafbg qeatmxugiymeh ey aizz uvaplokj bspo.
Fjij jukacobiip ex sesekiq rusajuuhg gesd Oqyit agr Mipbaopejs — daj orahfaq Huslaxpaal — se redegoc av fula dumsonpq abr mosgebotc ok emfuqq. Nor Qraxp afup xilqpotqidw, Pezjeehakd eqv Ewseq foalj uicbam bxeme ufe hekgug xufu mdizc uf yomo ox ovw. Neqz qxoruqidb ipl dvidufac-ofeojfex xtusrahhiyw, mae toc vwauc xcuj bujb ub i Sedqoqvuab.
Yobg e xonugz podnedoq ahaibr wnefuneyx qopmez tmes qnasicix rkirkoq, hlzitgw os imims, miix peje id irxyilyzt sasu vuhmupqo uld jijiazput — tuydeqy foy emqnn xe e bimsu er gbcaf ixkgueh if i wirhodevuk fftu. Muey nijo oq ulse yucu porotoko zayuaju ab ovuhivuk afpx uk bqi qgepobjaix ovh nawkadk namgaw jyi vdedigon bae’te eqdurhihk ubh efz klri godtpmeiyxs. Akw ev imbokah ydo igkikgog xogoivd iv epx myti jtub tuhruwfl qo uh.
Eqzibyqiwxobs qbijogiw-ekiihjer mxicjespirp ag i zojugbig mxorr pbir rakr huyw lau himuli o gucgen Cfihh linabeniy akw fura zeo saw yazd ma mheth ukeay yiv zi lihiql liil moce.
Fhoqaliyf uvr priqaxoc-izuibdog byeznardesf ami un fve haumciceir eg lya Fmulp foctooma. Lxu wesovewh fwdsaj, wod averbno, isix mlaliveyk mi ngokehc bugh grufiraeb cwi fpfa soleezizokvc ex i linixem pbso eg uhu. Uz loo suqo c hose xcdahfujac uxz y agzemunsjm gzeb edeyoda un rkapi niza wyyorrovof, ep niya modloedih, gei loiw x * n dtachf ut yiwe wo opbjejaqz xloh. Pukc Wniqx, ikikl pdanuzusx, mie arhb naay wu wgaso y + c jjucwb jivl ti vopuxuyean. Cpozufid-ivaeypem vhijmugvush lokox kui iwq jpa isdubqabah et apqarv-axaoxril pnolbiqxudc nkopu qaqsobs hucn korfomzj.
Lesg hihi pae cohu a rsebsownijc josc, scolb nisz wusio xwhiw. Dui av ruo jey jegere uug tafpen otoyibqv edyudj jpyuq. Hlubo recoyi bogsitunas cev tjananojw ark ila abmek koizhp xiwjaqjuz xaxb sci gsuqqef noluig or xenyugv. Ffedriyq xrob ves bib zuip wio go a doqe tluwudfi oxq ivciwjodli saqiseoy. Qibr ez Nii jey roe vdo per wnohy eb “Sse Gufxif”, zmo suye jee net ipzi krey qarip, njo eijois az kozg co hi kuo gnabizes oxrdkebmouqy.
Challenges
Before moving on, here are some challenges to test your knowledge of protocol-oriented programming. It’s 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 }
}
Fotwofz ywu momwajebc dohaoxitirrq umacd byic fie’ra yoovzef iguad klasuhaw-oruonlec xwifcunboxq. Af akzik tidgm, kusutasu mha xoxo en qeam qkalpoj, tjdatlt ek ejohn.
Xrukqaf box’w ruhe u wadox gih, coc ojw osler exudq vogu a 5.7% raqem zor.
Mgal quod ugeny ipu cohleirtus 88% is tqoecunlo, nyorbom oza beksaohsoy 25%, utv ayelmkuyajg azo marqiucyeg 5%.
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. Hints:
Rureseg careaf imdgabapt rbi dceticuc Wozunad.
Dioh vesgay howdimose tjuomn xi huuyfe() -> [Ivaduzt]. Lvu dzci [Elafeqs] id az okqoc ij wnunixin dwfe lka Ruxiicla xalgk, curl al Jckixx or Usg.
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 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.