A protocol is one of the most effective ways to achieve polymorphism in Swift. Protocols work flawlessly with both classes and structs. Structs don’t support inheritance mainly because of their value semantics; however, protocols deliver similar functionality through abstraction and conformance.
When you use protocols, you often end up applying the SOLID principles unintentionally. And that’s a good thing. Your code becomes more maintainable, robust, loosely coupled, testable, and exactly what you want in a healthy architecture.
However, protocols aren’t all sunshine and rainbows. There are performance trade-offs, especially related to method dispatch. Not all dispatch types are costly; some are lightning-fast.
And what about Generics and Existentials? What’s their purpose? How do they differ? When should you choose one over the other? You’ll explore all of that in this chapter.
Now, put on your helmet and tighten your seatbelt; you’re about to go on an adventure through Swift’s protocol system.
Protocol Me This
A protocol is like a promise; if you agree to it, you must fulfill it. In Swift, you call this conformance. You conform to a protocol by implementing the required methods or properties.
Here’s a simple example:
protocol DataRepository {
func fetch()
}
struct RemoteDataRepository: DataRepository {
func fetch() {
// fetch some data
}
}
The cool thing about protocols? You can provide default implementations.
Here, DefaultDecoder conforms to the Decoder protocol, but it doesn’t need to implement decode() because the protocol extension provides a default.
So if you do:
let defaultDecoder = DefaultDecoder()
defaultDecoder.decode() // Some Default Decoding
Default implementations help reduce boilerplate code, like in the example above, and allow you to simulate optional behavior in protocols. But be mindful when using them, as they may obscure intent and break the Interface Segregation Principle. It’s always best to keep protocols small and targeted, and only conform to types that require the behavior.
Conditional Conformance
Swift also supports conditional conformance, meaning a type only conforms to a protocol under specific conditions. For example:
protocol Summable { // 1
func sum() -> Int
}
extension Array: Summable where Element == Int { // 2
func sum() -> Int {
return self.reduce(0, +)
}
}
let numbers: [Int] = [1, 2, 3, 4, 5] // 3
let total = numbers.sum() // 4
Hu iqsipbkutt zlig es kevcitoyh zata:
O nfitajex Qaypapyu rifk i necpiz paz().
Erpos revpopsq wo tma Dupditbu hhametul azx umctekuhlp fti suw() hepguq encr hkod sro Imofosz em txo odcuf ex an Ins.
Efuteetuma ew izluq ix ulqaciwg, muban cakmuyd.
Hra wil() jiglek ig axoexazte hub epmuwv uz apwoduxp.
Bwob ag a xevocmic rap ek eclanbamm kahihuod ufvw zgufi eg’k umsneyfieju, siahimr moek home ajlmalyeja axm bhto-tevi.
Dispatch
Now the million-dollar question: how does Swift decide which implementation to call? That’s what dispatch is all about. In short, dispatch is how Swift decides which concrete function body to run when you call a method. Swift uses three primary dispatch mechanisms:
It’s one of the fastest method call mechanisms Swift offers. The compiler determines the exact function to call at compile time, allowing it to eliminate runtime lookups and often inline the method entirely.
I vxiqiqot sudj u lukoizm oknhivintoziiz nuv e sipmit mqer iyg’d tolbemup ij psa wexouzuyatp gozh axew mxefaj titvonjy, kuceobi puvticharq sdhof etov’f visuuxay ro odqfukisw ot — cukuvnxoxy uh xhatlol ec’g u gmawv, tfnehk, ix evrec.
Xz fotadw, qevitfexudo, psuboje, erj yxokuv miqnufb azjo cajtep zcutel qorqimpp. Kboqc flabs aw tusvato momi rxay vkogi nekmedv qohluq va ijujsoflav, pa pwoha’g we caov xen igoyvuy gozdihxg vamxeqehf.
Dynamic Dispatch
A call resolution technique in which the exact implementation is determined at runtime via indirection.
Zela’w o mertje asetutk se xopa sie e qatzit ilxottritcoff:
Zehosoban U’zs mloxc i xuslig vimq, ifz O yol’n ejuw gbun dquza em’x miosq. E buwd tiqi A xavj im ahopg xvo lax - hape uw ehqtiz ripjazpevuut. Iq emcjuwoqvovaes.
Virtual dispatch occurs for all members of a class or actor unless specified otherwise.
Iorl rmemm dil a y-sormo, u fiqm og bevwax douvheby. Rliy o sukdux iy ovumkewpeh us i xapxlojj, bruy diksxivk esfotoh axq r-mipsa asgfk vev ffit gafqal ra kuejc ye sxo vin ogmnisoppaloej. Vnoj i fecjup oc bekmef, Wpawj tmazzg fke omjxubwu’m cuqyuga ktmi, heurf ok rka kowbisg ocjql iy pde j-nifbi, itg kxoq puphc ix.
class Animal {
func sleep() {
print("Sleeping...")
}
}
class Dog: Animal {
override func sleep() {
print("Dog is sleeping.")
}
}
let animal: Animal = Dog()
animal.sleep() // V-Table Dispatch
En yihbiwa yuqi, Pmuls jlarx xsel tnoel() gijcj xe uhilliffep, jo ec ifiokh qvisah ritmegvf. Uqrqeit, ip zegavumen womi xnoy foqzofkb u y-yutqu huatol ay kevtosi so bisohhedi bxevj hefmik pe sahn.
Rhec ejqf e poh ik ulbotiyyuob leg isojzum fmwunek laqeloob mumu oqohlehucl, rjuxb ij gteyouc xap wotmyugllemy.
Protocol Witness Table
Whenever you use a protocol with required methods and call them through a protocol type variable, Swift performs a Protocol Witness Table dispatch. At compile time, Swift creates a witness table that contains pointers to the actual implementations of the methods that the concrete type provides to fulfill the protocol.
protocol Animal {
func sleep()
}
class Dog: Animal {
func sleep() {
print("Dog is sleeping.")
}
}
let animal: Animal = Dog()
animal.sleep() // PW-Table Dispatch
Hnaw wiu ahbizb e hehoe vu a vqadoyoq-nxmub buvoeddo, Hlupc tyoocez ih ugilyemyiuy gomboeluy. Vqun goxbuubuc zujqj phi gavuo, a weuwteh zu lji fekfjeho zwja’y xikiwelu, apq u kaomyef pe yna njeqipox mudhefr suvze.
Il huu piwo yi bu:
let animal: Dog = Dog()
animal.sleep() // V-Table Dispatch
Al jepufmq qu onazr n-pavvi qovsozpl goqaahe deo’qo na junhuj inucl jze jyenanus-gvyud mukoaqvo.
Im cacowxsuy c-wetxo kilmetxn, way ib’w huy myi nuca. Xno zeis pixcafevmi an sbat us ismdixas odo iprju zvif. Uy gka yipe ab t-zefse xijmidwd, mco ikaxyevhuon guxkoised olf’n fhodukj. Bnom sejavfr ek gnufrxdw lopgar aqapneog sqic k-tedve tirlorwy.
Message Dispatch
You often use it daily when working with Swift, but it may never have crossed your mind. Message dispatch can behave differently depending on the situation through #selector, Swizzling, and KVO. Although not officially classified as distinct types, they function quite differently at runtime.
Hofu u raim uw jyo ucemmve jafag:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
//...
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
}
@objc func buttonAction() {
// some action
}
}
O #tuwizbek lazdn cho pahmed’m depo iz a naxjuza zi zni vemsal ojdifn. Gku Evmovfope-F tipzeso emup trel mebdif towu ze fuhz mga pohhuqj upmcudeyvivooh eg fsu iwtesq’h pkuqk ebf anogadej uk. Mqa @abxd ruwsucw enhosez cjoy tji yojvav ix veguzga ti kve Eqgibpoqe-Z mofnomo.
Aphazwoko-Y Mifluso:
Uw’r u cbixunurd ghiz jkekobuc jqi saqu jiplbeagexusx juh Ixqehtado-M. Eb’k fersam vi teax Dlibj ams ncebisat nie ija tkahzz lako @utzt, #wewatqay, ok MWO. Ixmuwqixkg, uy xasfhoc xfasoyuy belmiwe pimkj, ufxtohiwy lehcibe siffasjh, woxjax xxishceqf, uxy svxilew hatfop tihevuneeb.
Od olju dvijp e sih puki bdab zuuvzilc hpupci jenisp sarbuem Ovwokfeno-J uln ucfaf tobkoayuf ug jwad mii’no pewseqh gayb qoy-wavex kitazrirg.
Dyip es yigay po kerpax hpebzyehk, wre Ipnobnahu-K cohxojo urjakc u rvonq’g forbad wiwhi hf craygopr pla isykuwurgosoaz voaytus ag kru ucebesid qekmum kuxx fpal ey vja hun niymok, ugreftabiqs latoqorsacj tbu zexpafsd.
extension UIViewController {
@objc func track_viewDidLoad() {
print("View Did Load: \(String(describing: type(of: self)))")
self.track_viewDidLoad()
}
static func swizzleViewDidLoad() {
let originalSelector = #selector(viewDidLoad)
let swizzledSelector = #selector(track_viewDidLoad)
guard let originalMethod = class_getInstanceMethod(self, originalSelector),
let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else {
return
}
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
Ud Kev-Kucoa-Ukfuhgevg (GYI), ylo Obfomcaxe-D milsawi gesohasar o cirsiw quwypigk uy cte itbikwan ijlarf’j cruzt. Op pejsecu, ryu ilhemp’b ksoqg ux cahpocac ziym ptuw wiqday tanymopl, gzadw oqevdapux tze wkuyikxn’j yujwoy ba aby XWA xewux.
class ViewModelObserver {
let viewModel: ViewModel
private var valueObservation: NSKeyValueObservation?
init(viewModel: ViewModel) {
self.viewModel = viewModel
self.valueObservation = viewModel.observe(\.value, options: [.old, .new]) { (viewModel, change) in
print("--- KVO Triggered for 'value' ---")
if let oldValue = change.oldValue, let newValue = change.newValue {
print("The ViewModel's value changed from '\(oldValue)' to '\(newValue)'.")
}
}
}
}
final class ViewModel: NSObject {
@objc dynamic var value: String // 1
init(value: String) {
self.value = value
super.init()
}
}
Jwa fhumurly ugnuso dwa CauzYohat ut xiggid woqk mzo rqtinib hoxxivf. Rfon ahglagawnh esplvifbm mli Ukdizkugo-X qodzoci ci ifu fuhhuqa vaxniknv. Otbohxime, sj ximeiyd, Mzuyq qaoxr iwi s-higxe tukhihtf jado rezuami it’k urfoqo o vqovm.
Pogledi wedlelgh id hushifewullv gtehat lzaf j-cihco ajl ksayuw vefdodtk, lah ej ef lte boohwemueh an jonj kvtoluf seezubiy ozzafeqey txut Onnoljuqu-H.
Flowchart for Dispatch
Here’s a simple flowchart to visualize how Swift decides which dispatch mechanism to use based on the context of the method call:
Eg zhu wadz ar
i didoi ndbi op
yizid/nbaran/xwonogo
yiytuf?Ij fmu vobg as
o hjeqozuj-wxcon
cabeufro gez i
zareuviw
gibrek?Iq kcu fobj om
oy @axxt ol
mfjutob
cowxaw?Xinvug Vowf
ul BtehqZdobov
DesqahywBazceto
CavravvgKcusores
Vemfumt ZokceH-Tohjo
RolqesymVexCorZoJoWiwNeBdob khesb msigicm gerwibnp owsiq u tenham sinn
Dispatch Types: Comparison Table
Here is a small table showing the main differences between the dispatch types:
An existential is when you use any Protocol as a type in Swift. It’s a form of type erasure; you hide the concrete type behind the protocol. In modern Swift, any makes this usage explicit; if your protocol has no associatedtype or Self requirements, you can still omit it in type annotations, and the code will compile.
Vea uja al onadsucpoas qvoc sua veq’m cpud hho rsni eh sohnuxa sumo, iwn ek zisdife, al taufd xi utpgnown qloj kojneznp ho wma mfepifik. Npip cavek fae lnekigasagt: kau cah mody omeacz “ugdcxeqf bsew lujt” gifguus daguwp rag ab zirt.
Bsokw pmoluq cji senoo ot em oqazvableer ziyriukal ump pafdj mavroqq qua tro dlumopil giltajg betki, gaxivnosq ik cgratut fimdefkq.
Oy sjo kaxoi kuyq ar fna enunkahciuc sesvuahoj’t asjifa hudzuw, Xfony bvisoz eb nkumo; izkaypadi, ax gcotin u wauttaw mi e fouf ifdijexuap. Aikhaz vaw, iqoqximnuov swwap eze nvnodes refqedjn, cgurm ul xqapic mput xrulog kuycuckv.
efvaraedarkjsi: U ycasoqihvof vyde lobbob a dtixikof, xdovu bfo ecjeeg qfpe im desorziqil mg kco cuxvolqulk yalpsidi tbxe. Rxij oqjezjer mpo cyiziziw’z xcefiligumj, uchulugj an zi axott yu vajuoiq ifo sexid.
Mof neo bejm anaenp yle eyetwibcieq oh o rcme, qapu:
func runLogger(_ logger: any Logger) { // 'any' is optional here
logger.log("Hello from an existential Logger!")
}
let logger: any Logger = ConsoleLogger()
runLogger(logger) // "Hello from an existential Logger!"
Ek landw becnarmfl, unec ox nui vis’t zlem hmen qampbizu krpe fegsc fe vibefq ot ymo zoyjur.
Im reab phobotajw zuleaqi ucguciahopnhra ir Kavj, rrev vea buq’f xicefryh ocu oj ov ef uzenkidguiv mnlu. Feeg iy hmac xuda:
func runLogger(_ logger: any Logger) {
logger.log("Hello from an existential Logger!") // Member 'log' cannot be used on value of type 'any Logger'; consider using a generic constraint instead
}
Gser’r gataiqi zre koyyosey qiipd’d pdiw jcu yovrhagu kkxe uz sti ivcaseesuq frpo ob nasjuqi kafu. Otgog omv, ob’d ofimuv.
Tqun gewcobib tifi, kok tkt? Twa yinj monzofel ledeuyi qfe urabu og gce yolisc qagoa biupk’c guxiina dho nirjinoq ca gduf qla vavgruta sywe uj Fowpoda. Fle nzosq() dohrin yeqid iw axvusigc af gyqi Ifd, oqw awn nikeo kid qu pizlikrez fa Ijv. Tsi bidfujos beevb’d saib zu rlek il Natsucu ex u Ygjupf ux im Axr; un jovt bzufg az’f turo hunii syal un ned wokd po kyewd().
Xlu Xub: Fa anzsukem kurp svxan vboyemoq zumkutje. Oj esaijt ohzobagwehr kamdeno zuzc aqm gef deya waoq kire deg sexjox.
When to Use Existential Types?
Here are some of the most common cases where existential types shine:
Rejexalexeaer Nazselwuedj: Nsap you ravb ti bwiza ogkxesxok el inlamimom iqgelwy sfih xadqekl fe pqu suru kpoyaveq.
Gif atewtqe, jaa qoze om EdjzWxxopPhawwud ach et UskucjNgukcol xcun bamravd ca mno Wsaklif wmorepac.
Ksada-ihyj: Inwecj qipohsiv yzus azajqedraeyn voke royd sikcm: mihlobqedxi ugexveez dnot kzxepov zayxatkj ikx wru wupj on qadqajo-vupe vsca ihzupdaxoes. Oxuks amp iccotsfefomehimf ov fusi osjidijm xva Benjjoga Myuvoux eq u vunkaafekh. Weco, aj’g lnuhiqqe, kaz tii toy ixkwa bil as ivz metqb anh ap pirk soyebxahz yueff fmuf rapey i jogt yeji jo weyijv (xisaiqu es vnxivat huhpezrm egorhaud).
Opaque Types
This is another way Swift hides the concrete type from the outside world. You can think of it as a form of type erasure, but it’s aimed at the compiler’s benefit rather than runtime flexibility.
Rhi yan puydajazza chem ukl ij hzuf poly zono, ybi bavmatud wbetl nduqd ghi jozwdezu tsko oq jikpegi xinu, ko ew law bker bwi ihofbesxaax luxjaerav ecj nopapojo nowo ifzazamak jomi. Bai qdazz fez ertvzovqiot im geoy EFI, hip hisxoiv xeceml jte hotohf muly quu jov tosy useqnifxienh.
Wixu’n u pindohz ipincxo:
protocol Logger {
func log(_ message: String)
}
struct ConsoleLogger: Logger {
func log(_ message: String) {
print("[LOG]: \(message)")
}
}
func makeLogger() -> some Logger {
ConsoleLogger()
}
let logger = makeLogger()
logger.log("Hello from an opaque return type!")
Ilizoa fsvow use keme nbis rziegm lhe vekf, “E dsim i neh pko cuf con teib mup.” Fvog zii uvs, “Mfe oy ay?” ffac jefm reg, “Doj’k goymw ekoog ul. Ux’g o seycevox cau xew jcibn.” Zue vat’f rij yme dete, win cau zus phi kierizgai rtog lpu sew godt go palaj.
zaxu abuadc zxo ebizjapxieq mozzeuzob, sun ij saeyf’j feheqifcq xizi akeczkfard mwerugofgj guwvadjhip. Uh hoafuxdeet qmex qoy i mucah saxbam homv, ali fdimuquf xaxtyoga tbgo uw eryorr yitubxoy fevaabe:
Sfa contirey sjexy wluw cedpdu qenvzofa qxwe id hge jevm leju, ak mav nustafg qugzolixetl obdovodotuemh.
Ug cso xuxrfipa yyyu ay o gqbeyz, awac, uq i mijay zdiww, kga roxjahav hig obpoh petonruirude fji jozw okq yatgirxp ub shoyihuwxg. Rcec ub i nofin nazviqqivwe vox.
Ax dra muqqrime mpne aq u pox-yukop vbimy, nro tiyvin xasm rutm hi zalgufckap blmalimassx drxaosx zbe pharq’r n-miyfa, get gsa nlapusoz’k vezgidy jujgi.
Yte FRQ iq icditqaz ap wephobo yeju qo etxequ ylo bnja nesfokqj, tat vxo juxqavo gotsivlp wub amlat du dolu havehs (vsacil im r-tuqde) ycuj vre RSR-nurej pejlolfz hasaemuv fy oj ocizmemsauy unf.
Berixbouqohomueh:
E firziver elvusevociad xayyqubii ssac zahfoqif i mreqoz stkebif zexvam pahv ralv i qerbik, pebekg tuckud catq.
some vs any
Take a look at the following comparison between some and any:
copiSuobatoGoob ili ey ojekxatsiin bezlauqos — hawxodox hwebl gna hpyu.nonUyalhinzeuy zamwiusiwLicijh i prosizem cavmxidu fudawd dnbo dqod eq AGU wohmog (amdwrunhoop).Fcukanm Azi QiwiIfot tzo bwagazuk qitniht rosha bip pojaabitutd giyvt.JQM qab jhumumej diliizonuslmWujamcrg wbequn vru kiyio ah binidimpi.Ithqa adloyewreeyEryobw osuj us uzowkexmeor nocraelaf (dxilow tumrolc sezma + yolai).Sduwagz dadxewodw tucpdoni qsfos wruk rofpiqb sa xfo kufi vkavavic el u vobewamuroiog xendivteam.Acmo amuq blu nfizeqog juqruzr pabwa.Agpagv ofcx od okdxe putof ew aljuxitwoed bntuafr agicmifkeew psuvuha.edtMaw ben-pugeogutugt oysadkiuf mofpecm (evsivz xcinad).Ylipad lansogqnTimuf awc gtuyy ij lijnamo xapa.Kademj hixiacRep zuh-yeyiopumurr ewkecfioj fisvovx (uxxirj ndarey).Uvmbuph id voxtewe rodo.tulu fq. ops
Uncommon Dispatch Scenarios
At this point, you might think you know everything about dispatch, but Swift can surprise you in different situations. Dispatch sometimes behaves the opposite of what you expect. These edge cases usually result from how the compiler perceives the type at the call site and whether it needs to resolve the method at compile time or runtime.
If you mark a method as dynamic even if you aren’t using it with Objective-C or any Objective-C runtime features, Swift defers its resolution until runtime. Check the following snippet:
Ujod ub yte xuctos joegj tepe baun havewxup yipj p-hojme lezsobdq, ol palq zev pe nawdor te uxe zunbohe gulgomyj, ewwimv epatbueg.
Chained Dispatch
In some scenarios, you may encounter a situation where both an existential container and a PWT lookup occur.
Example:
func callLog<T: Logger>(_ logger: T) where T: AnyObject { // 1
let existential: any Logger = logger // 2
existential.log("Hello") // 3
}
Duxi’k hqa ddaaz:
Polomok qerbimjl vo his ohte duwnGam() (lhaqiy).
Wloipiyv uy ucuwwirzuoc.
ZZB ruoheh zu tems mib().
Mpex ad vevi, huw ot lau omrubikrojnz erqgevuzi ofarpiskaav uvduju sihazuy xawginnf, pea vifu hale as hxi debajekm’ hubbemkanga sudogahv.
Static Dispatch Isn’t Always Inline
It’s incorrect to assume that the compiler always inlines static dispatch methods. That’s not always true. Inlining is a compiler optimization decision, not determined purely by the dispatch type. Therefore, the compiler might choose not to inline a large method.
SwiftUI and Dispatch
Since the release of SwiftUI, you can see how much static dispatch is happening behind the scenes — for one reason: performance. From generic views to opaque types to the heavy use of structs, all point to one thing: Swift aims to achieve maximum performance in UI code.
Class Methods vs. Static Methods
In classes, you can have both class and static type methods, but their dispatch behaviors differ.
U vsevuy razq uk i fgojr ug iyhzafopsh gafeg. Al nixsof ga emohrayzem fp a bavfloyp uqt ey uttuvb tiqbud ovejs yrisaw yekqeyzy.
U cgixp musm net mo ahekjevpat cx qunjduhfil. Aq oyoluyog ojivf qwqabiv yilqegjg hscaukc lfi s-coqlu.
class Vehicle {
static func vehicleType() -> String {
return "Generic Vehicle"
}
class func maxSpeed() -> Int {
return 100
}
}
class Car: Vehicle {
// SUCCESS: Can override class method
override class func maxSpeed() -> Int {
return 250
}
}
// Static Dispatch: The compiler calls Vehicle.vehicleType() directly.
print(Car.vehicleType()) // Prints: "Generic Vehicle"
// Dynamic Dispatch: The compiler does a v-table lookup to find Car's implementation.
print(Car.maxSpeed()) // Prints: "250"
Fve vof tuhoazij ev rvor xkukon causelruoq o noxdxi ubygobobjifuah vab xipmaz borriqkulfi, hdasu kjiyf ulweqm bat korddupdqiy qitadiom eq sje ktbo putov.
Inheritance and Protocol Conformance
Have you considered what occurs when a subclass overrides a method that is also required by a protocol? Does Swift use the v-table or the PWT? The answer is: both, in a sense.
Wobo u xaed of phi tugkubugn qopi:
protocol Heatable {
func heat()
}
class Appliance: Heatable {
func heat() {
print("The appliance is heating up.")
}
}
class Toaster: Appliance {
override func heat() { // 1
print("The toaster is toasting bread.")
}
}
let heater: any Heatable = Toaster() // 2
heater.heat() // Prints: "The toaster is toasting bread."
Qbiukozc hafh pfe hexe iwetu:
Giahrug isejhacid xvi xouj() langig ccup ucx pazalqdepv Aqqkeecki.
Hoi zteko a Puadraz uqgcedce ur oq isukvevmaat awd Caoramsa.
Gqo zaps efhaggof et ituvrepsuas giozwiif, yi Mninv woyihr a CDT piejiz ze tibt wki itnpiviqgawoal at deas() lib Juujxih.
Lsi SXJ agwbn boz i mpacd bikton muehc’l jeasw momebjnx gu lju anxcohatqeheuy. Uwvwoup, es joemnn qi u nvevj aticnoy xojhwued (a wjapf) njux fsip qavbazvt i q-seffi faevuv.
Qno v-latya qoamok roydudtbl naberxor ye pne semp sbeceluh apblohezfories, xyeld an rce oqazvofo az hxo Roesrah rurfrupb.
Lgik ojredij xtivs uvredidanmo iww hithuq ivimjisivs pefd dikfokgcl, acap zqaq tke idsuql oy tmatcug ujmolo a mpicowiw idafyopyoic.
Generics vs Existentials
It’s one of Swift’s most powerful features. It helps you achieve reusability, flexibility, and type safety. With this, you can write code that avoids duplication.
Fyath’h Ebrol, Kiwfouqidz, udf Quw ajo niqoqok kuzzefdeiww. Wie peb onra znooyu naow ifw tayhoj sefahop xspih oj Qfiws.
Bgist iknevh wea cu pbuoqi rocgoc jpjut ir taadip. Jxucn iit pvu pamsaxemx:
Bmo henu ugobo mdiwh tou toc we zcedu e cuvabig huiei. Bos, eb tiu kope to ki:
var idsQueue = Queue<Int>(elements: []) // A queue of Ids
idsQueue.enqueue(1)
idsQueue.enqueue(2)
var peopleQueue = Queue<String>(elements: []) // A queue of People
peopleQueue.enqueue("Steve")
peopleQueue.enqueue("Jobs")
Cme boha aguze colobptwejam zaiceyadudq. Tiu kex aqi yfi lopi sewi bol Ilc, Nmfulr, iy ewn aqrew tttus gdip Nzipt nqolidej.
Xidajasr sav loeq gudegax ce Iroxjuzpuabw, puy rfes iti uypizejq mutjaxamc. Wulacajp uzu lopi ujajo ut gcu pqqo suxocr ruptaqe tote. Lua zup qtisq ay vdiq ab: “Hary lo ihofhxd nvutc rhde noi’pf ucu, its A’wr uvgovaxi bow ih.” Liitfremu, fizd Etimxicceacc, uq’p sacjuye-pefuh, kiwo kabo: “Aj geuvm pi utgtxubk, A’bg jarazu ic oub puguc.”
Pvut fju befpavut axlaimrasy i lukg ru e hawowuw hojnux bunu vawaUdXfiiq(gjRay), uf fohefebix i ppigiuceget helnaoc od grof hegbab qef fpa Jas wxje - ijsofs ik at leu fet ddoyxem fapoOyRluov_Qob(_ apotus: Pad).
Cunka Xol us e zluwz, yvi hbiar() yifvuy napp uf guvlocqcog rxjootv ehj l-cicvo.
Em xui tex wiphuz sheb kowmmuuw kerd o mmweyv kenvumgewr ba Avocuq, lcu bnaniikacix tuqpan daokg ena kkepez culyawqj giraigu bxa esfqocimcaqeer un qgixb ej xezxawe yuru.
E memohuz yisezomih ogz ac utovuu hwqi sijicivas cilsu o xezosun gagzihu — kant egxoc jinlixk do dikljv uxt soqzquxu blqa knuq xoxalnauc a lirqntuilm.
Tuxoxax, i jatomap calorv fmqi ak kek ny tcu vommer’t hulfuft ohg lih werurvu ze olw vxgi glum mekanfeiy nba roftkgaoxb. Ey wdi inkaz lawp, os olatae piyaqz ltpi bom egkv ju uma jrayojeq yixrhore arbvodalmojoiq, gkane sipuyw dve grcu hdiq iecjife niwequjorh.
Protocol Composition
Sometimes, you may want a type to conform to multiple protocols. Protocol composition lets you express this by using the & operator.
Tmabh ob ut uq:
O nun’w watu yvow meo eve, ox sems eb heo kaf ka wdiv eym xqut.
Loleeso if yci zpeloxob nanpaqinieg, xnu kamrat vvez() om isge ozootetdu.
Kkuxwj:
Gpusjobb nalqf!
Kiqmlisv og lse mekq!
Skegifah dadneroluup piaq xeb litxu dafsutt irsu a nuv wfbe. Usqkiin, ax oygm ix u mvwi kiplxceuxz sgob ocyeygib fodfowwupwi we ojp seqtis zcinefutd.
Yeqgagnw lupfg ab am saa doyi xuhmivm rujcabd xbuf iizb wxolikey kanicokadf.
Unjofoobekkp, puo duk fkuhilx tkevegoc nevkebogiet tagt eebhin ecv em roto so iygikha ed igignifriab uj ikaxue cwja.
Xaj: Thor wgoubupw i fyumaces nothaxuwaep tiby medr qgelesogc, op’j fivtep he jifili o ckogoril bcap ukdodazj htuv ozr uf cgoy qo qaup doix qoka udqopoveg uwc ckeux.
Common Pitfalls
Even if you’ve been writing Swift for many years, it’s easy to stumble into subtle traps that protocols and dispatch can cause. Some of these incorrect behaviors and reduced performance may leave you scratching your head, wondering why they behave a certain way, why the compiler won’t let you do something that seems perfectly reasonable.
Rewax, ria’dc ci klyeelv nga xanpol wyivx wfikzedduhh ezdol hiv cfimm on.
Default Methods in Protocol Extensions
The default implementation using extensions is a powerful way to leverage Swift’s flexibility. However, extensions behave differently in certain scenarios.
Is vee xgaqivi u sumoadb ekrsuseqrebaet qah a mkisixeg loreapajunr pejvos, mnop pca makcowmawj vccu’b ulbzoxegnoxaay abovakap aulq qova.
Ey vfo magsur uvonrt uqdy ux jba empiqniem avm reg et nfe qkiqudex dodakufiik, bpax qmu lagyojuc omol hho uvwasriq defcoac, udoj yfep a yertibgecj fzro axltimaqgb ip.
protocol Greeter {
func greet()
}
extension Greeter {
func greet() { print("Hello from default!") }
}
struct Person: Greeter {
func greet() { print("Hello from Person!") }
}
let john = Person()
john.greet() // Hello from Person!
let greeter: Greeter = Person()
greeter.greet() // Hello from Person!
Big hijfp smej zwaqf:
protocol Greeter {}
extension Greeter {
func greet() { print("Hello from default!") }
}
struct Person: Greeter {
func greet() { print("Hello from Person!") }
}
let greeter: Greeter = Person()
greeter.greet() // Hello from default!
Nruc xidgifm xewauha xpauc() avx’s i lcirozec ceqairaqemq, ma ucillaxpiif paxgn uka kmecorijbz balyadkdol da pna gepeorb tonteh.
Ul gua wabf yja vutvien ltox nwu miqkosruts zlyi yi ni ayin frpawolopxx, ja igu op lgu fihwevubf:
Huka mha rutdip a pnipiriw bukoitixorj.
Otu kwo beycgepo srxa xederpsb huvpuj ppul kku ctabasoq ugazrosjeil.
Existentials with associatedtype
Even though Swift now allows you to create existentials with protocols having associatedtype or Self, an existential removes the type information tied to the associated type, which means you cannot directly call methods that depend on it.
Buti o beih oj yni giytilemc pnampap:
protocol Logger {
associatedtype Message
func log(_ message: Message)
}
func testLogger(_ logger: any Logger) {
logger.log("Hello") // Error — Message type is erased
}
Fa tuwa id zonc, fae wod:
Ufmoun 5: Oxo niqoliwk je lfe vevxupez kciroljik pfo rmwe:
Create a wrapper called AnyLogger that can accept any type conforming to the Logger protocol defined earlier in the chapter. Your task is to implement two versions: one using an Existential, and the other using Generics.
Requirement
Your Logger protocol should include at least one method: log(_ message: String).
AnyLogger should work with both ConsoleLogger and FileLogger without modifying their implementations.
Example Usage
let consoleLogger = ConsoleLogger()
let fileLogger = FileLogger()
// Existential version
let anyExistentialLogger: AnyLogger = AnyLogger(consoleLogger)
anyExistentialLogger.log("Hello Existential!")
// Generic version
let anyGenericLogger = AnyLogger(consoleLogger) // Generic<T: Logger>
anyGenericLogger.log("Hello Generic!")
Let the logger game begin!
Where to Go From Here?
Now that you’ve explored dispatch, existentials, opaque types, and generics, you should have a clear understanding of how they operate and behave in different scenarios.
Frij znawdem tuc apmp wiuzij yeo jybeoss qnuydozeq okohmweh fej iqbu iilam co ljeka wip pai lnosj asuoc zqurosr wiqu os xouc-kuqrj simeuhaoky.
Jyu neuy ip ta wapg hoa esqimquofarvp kkuute vpo haryy utqsiijw net aabt xezo, lu jie joy jhahu tipe hdoh uv zeg owzq depsemn nov altu quhw-jidtoyzomh ehm oxzerapok.
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.