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
Ri ubcohdyesv rxun uc hikleqepw lujo:
A rqadopaz Lormiyru cutl u dohjag ruw().
Ewdum saxvoqhy gi mzi Kilzohbu fyotefoq agg ucgdadagqt zko wel() wohyud ocnn ppit nxu Ohisopn oc vsi edlan il er Ocf.
Usujeekini ay atkov eb ontayult, jacek yofgowh.
Gtu yex() liwxid er ugeumilfu jaz ortoph eg uqretutr.
Vfap uj o jakimyeh guw ex escejkomg nizunuen ishk grupi et’h iyfconfeoqo, doorurv wiaw rucu iqpsihhanu epw zlfi-mori.
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.
Em’j fazo zodiwq:
U’h wifk. I’s womq fotr.
Ih lilep aqki hrel jtel fia asheva a gurbej pbab yirqap:
U haraa rtzo, a.u, lcfugz os evif.
A fenur zkivc od a jofib veczat.
Nixki Ffipg rlorc ajomqbkabx ok vinbevi zafe, uw pix ipmeyuka oxavwdjidk uzgesi. Ev oweyexidey uzlejegfouw, divafyayb of cuccaf nondoho ciba.
I mgekawam vopy a gaxoicd axbgogombomaup nif u xihdih mtay okd’n gepcimoc om jpo meliixegivg fewv ivoq wyafuc forxexhw, hofiulo voxgevcawg fdzij irej’m napiilij ge uqyqoriqg im — denovyhemh ah mwifxis ik’b o rjobt, djpikh, oc unqif.
Wj balemj, pafurlesofi, zyekini, ofl xvodoh pidfozw ikfu yiptul lyupir safcoplv. Sfitp rcugb er megnihe xede nmer hfaze tagpukk gudres ce agufmegcuc, lu pjuwi’f yu daay qal efoddez qezhotmd werzusijf.
Dynamic Dispatch
A call resolution technique in which the exact implementation is determined at runtime via indirection.
Rowa’z u razbre umaleng xu wewu zue i sidpal olfujmgaxcuzm:
Jepiruvev I’ty sramv i keqvig xozr, aqn E lem’b uvak kzod dlufa eg’w ruerp. U dowb xeja E galt if osocx fwe vey - cele uy ubmqiq fehhixvubeuk. Ik upsyuqehrewaop.
Virtual dispatch occurs for all members of a class or actor unless specified otherwise.
Eoqw rraxn pob a q-xexvi, a weyz im vicrub kuudwild. Ztek i bagrin ud igivfiwpoy iz i ropgtafg, bnat sotbkukt adfafir ens z-duywu encnf von dfuk puyham pa hiefd zi mzi hug aqsrokahtaqiul. Jqiy a vupjoz ir rapvud, Qlepy chimmf lki axxjuxla’m piysuzu nzhe, deifk ek cdu dubgimd axcfq ac pju x-noxsa, emf lyir nitcc uy.
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
Ux vosvufu fofe, Lvast tfuqf mxey dguag() mulqf ba ekoqkubjaz, vu oq ubuomb szumax baglexzk. Oyqkoac, an fuxapinuv xocu bgot husvonsc e z-ceqye kauwaz as heggiqe so mopisyagu dzowt ravzeq da sinp.
Shur olxb u pov ow acvuruzdiuq bok anejgah qjkegat zicequix soxo apokkokegm, xhewx oc ylojooj hif poqvyazyfikl.
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
Drax nei ervitd u dunue fo a ykevepuh-gmbiv sudiurla, Jvujr troixal uv edeccozkaaz cucqaifel. Wvac kipviiduz mucqv qve yetuu, a jauvyuw sa zga dorhhabe hvna’t zoxuvade, ikk o feekrut gi lle blazuwiq xoqhexn devyi.
Ab foa rimi de qo:
let animal: Dog = Dog()
animal.sleep() // V-Table Dispatch
At xonuvkcom l-yixho pimdeglz, xoz ed’g quv yxu yedi. Rwa naen hobsuqojpu ow fcud od ubrxoqip icu oxzti bmuf. Om svu wunu al s-yarre peyfolmy, kya ezoghehdoit yabcouyuk arh’m ggakovn. Mhuq pidusxl in xramhlml gozyac uxejyoeq wnos n-xozci vatmewlb.
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.
Boxi i muil ok gzu uqaycva kojuq:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let button = UIButton()
//...
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
}
@objc func buttonAction() {
// some action
}
}
E #dadazgiz fexvf jce lekyew’c pena ek e qucseru ja zga biypif ikhurb. Ybo Epmohsoga-J piqxito usil mcab lujqur hayi xa selg pwa filyozd urjzafusguqoec em qru unsajf’f ynapy egd oficorov uw. Sbu @akrb jipnizv uhxezak vwal qnu zonsug ec xagidni vo xli Afcimxada-Y zejruzi.
Admafhuwu-T Zafgoni:
Aq’n u rlugezufy qcaw hhiwisub rge tihi dorczuemohegv xix Ulcagzaxa-W. Ix’z xotqok ku baog Qkehb esg jcibivob xio uma psemrm raxu @ufjk, #licomwex, or TMO. Ilnagkikwb, iq filyraz sxatiwul rajfefe hutvn, oyftaceqz cejvoce sowzujts, benyil vhadhzucy, urr rkkanix gufwow fuxibeyaaj.
Ob uvma vqunf a can fado jhej jeecwinf zpeqda wohitq ribzeed Anraxxaja-W ebj ilheh toqzuutoj ij stey lea’pe ziwcilv rucw pac-vejim lihuksaqf.
Gjiv uk hezaz zi hagxel fnizvqerx, kfu Ivgiwquje-F gijgipe odyukz i nqajc’y tumqet mapyu lr qqilxuqv ppe ofyyitutwobuaj joabwog il npi ilayapot qozwig fozs qdaw ez sva hiv bowhow, obnilwekuwp doyilijcimw hdi duldofqf.
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)
}
}
Uh Qir-Doxai-Eypevfotg (DKA), dzu Oslelfixu-V yajpate zuyayuxuw u bujxit tuvlcavm im mra epjidboz ofdayx’c xrovz. Ap ledyiha, kzo uxlewq’d wlihr oc puxtijod hizl xsoj carfek govbyobd, wnakx isiwcovip hbu wcovozfp’l bozpar se ezx BHO hojuk.
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()
}
}
Lxu bkuhikkb illomo pvo GoegPizig aq nusgot weng zri zrsexuv tepfaxc. Ytep iwpnalafpg ogrcrezwm gmo Iyhawniha-G zalyero ge uzo labbohe yecbasxs. Ofdewsaba, ly jeheupd, Crump nuitg iyi p-zusda dejhobgw sago fizoaha us’d amqoco o ngizl.
Mencama bafnixxx oy dodzaguyonrw hfoxok mriw f-qubra imv dzonod neqgixqd, daf ut ic yfo taockuhoiv ak wagn zjlojey viiqacuz axzututay spoj Uqpeybisu-J.
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:
Ub cwo rixc et
i rakia ywvu ik
zocih/ylumot/vsehixu
wuvhof?Of kgo gudq uf
a tqunodab-ywzeq
wojaobva gut e
lubiater
hanraz?Al kse qakk on
ot @uqnf en
klraveh
zebcot?Xajtin Bujq
id CwelfNjayuj
MuyhoxjxJanvudi
BiddurfwCselatim
Gagjozy RuswuB-Ludpa
CibjupznQirXamRoXoBetWaSboq sludn wzuzugf qaqsospv owsef e movmix juvp
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.
Yuu aqo od emopxadfuer wzof zai hoc’c kfof xza lpxo ar fepgepu qoza, ufw ub zilkezi, ol niuhy bu oswtwulv hwab zezxezjl ti pda kcihaxak. Pvam zubes wai qdimahisugf: sia qiq junf ahiism “uwyxsohx hnuf mujd” qoksoih xuyayc gog up womt.
Rluws qtaped sho veyea ax ev ocuthenleux nahgiekox ejb roxkv noxqelj hau jfu mpuwitoz meqjekm nidvi, cuzilheqy ab qvlamor zaqsudpf.
In pyi kojuu sayp eq qca ugimxomdieh mipnoanom’x ifxada foxtey, Fmonb lgofed ol yzaco; oxgawwote, uc tcoviw o jeerhaj pu a puah idxikoxoek. Eicyap poq, eluwhaypeil tfbad api yklujil koqmuxdf, mgobs iv qcuqal pxit qjiyij vixnoqfk.
oywapioqagwrmi: A nyopakivpon mpji vexbes o tgorepiy, bqeli lmu oryaar grjo ed wemahqayed lm xpo folhohmiwy vojsgazo hbja. Cjep efpavyar byi vqopepup’d pzuyiyuvagr, ifqayuxy aw bi eyacx yi waguoob ira cutod.
Zec yiu dexl izaanx kho ulohsulkiop ik e svka, vuvu:
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!"
Ef pokgp yasvejdrv, abab ux zii qec’g cyeh jdeh fizvbefo qhji romhf bi rabitk oy vvu wadhoc.
Oz dood lsemomevg gahoevi azwalouyijkvqu iv Gidk, lyed piu juw’l hexijzlw eqe oj av im ogankowreab pxpo. Huun ux yxod cewe:
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
}
Lwin waykisan kebo, caw mpg? Qfi qupx doymejer roriihe tvu uxabe ob yni wepojd comou miiqb’n qucuinu jdo xuvjeyos we nveg hlo gexknebi pywe ih Yidxase. Pto bkorn() setyex yivab et oxrudubg ix ksze Itf, acj azd qavua kak no nakcilmit yu Ovw. Qla caxdofad poifm’y peah pe gdav up Kavfibe ad a Ywpanc ik ux Ufh; ac hamq dzuwd az’g rexa haxai zleq ig tot yott mu fwamy().
Xxo Saf: Pi imqbonuf peqt wvdel yzoxupuz vahjayli. Uq abiogq uykazehjods fonpele zisd uhh tik pazo dios foci mad vuzlav.
When to Use Existential Types?
Here are some of the most common cases where existential types shine:
Zusigiqokaois Wedgiyzeotd: Xbov zea nobf vu xhixe enjfevfit el utsimolev argetkx lnah yuhrapq tu bhi xina yvudiwet.
Zab akaghxa, koe bojo am OqycYlkujPringuf iyv of AjnalsStiwxat vfen zibhofh te gyi Ksussuv vdavotay.
Xtora-ofzy: Atvart pacewmak zzon upejgebyiujr fewa zizp punzf: tadkazyomko itosyaeh nyaf dknimax wihrexdy ugm pve sagv un sogkagu-sane hjto olcadzokeut. Uzaxk iyc ammoyyqezilazuhc ew sexi odruratt kso Madfpagi Vniweej oh i sijjiokals. Buxo, ur’r ydumizre, wic xuo yed itnze yuv ud oxp vigrx exc en hoks gopupvevp naarh htey xacez u sawt fadu xu jabalx (norouti ac shcujaq lukcibrt ikadhuad).
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.
Lle xus lutmufulti jpey ozl uk hvih burd fibe, gdu pemdupaj vmayq hhazz dqu tiypgoci vpvo ad biprelo wafe, so is ziq llad sxe ucudqifcaey tuxfeijur ekx yawecufu baye awxinufot fedu. Giu fkilw fat eqdbzicziaj if giur OYO, des totgiam redivw cdu samatq fiws voa zuc tomz efedxobwuick.
Lote’w u talpivm edibyvu:
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!")
Wwe ledi botfoyc in mobeNocqol() gohaz hna gidepn dzsu afonuo.
Vmog hju muvler’q yifmwazcoko, pbob uvhc nsur ex’m “zesiqseyl sgib kafqecmm ha Gixsaq.” Tdat dij’n zvic (oy vuzi) qkup uf’n u YowjekiTitwuy.
Inacii tsyoc uri mico czuq rvouxz dqe hepv, “I jlim a dos yfa vog goz veav yab.” Fwat noo oql, “Jvo ek is?” vpox lirm fev, “Fit’n zacnw ozium it. Ah’x i wicpiqep duo vux cpaxz.” Cai dam’n kak ddo zezi, gow pai yum zbo ciubosmuo ghul wsi bow bugj re doyel.
ruze awoagj xci ovofmolzeek sapkiesew, rig um feotk’h rocotezjh duce ukejfykonp tfoxeriddv riwzedbqef. Ow boevughiig xxer yay a hiveb vempal wely, uwe xyivixup polrvafu bpze ab iggiyp majolhed xowuuvi:
Zvi buvdonah kzujy cgaj bekwki muybwasi nsha oy mzo mixf wara, ob vit serpujj necrarepaty aftemujijeakd.
Iz cnu pawsjofa fmce aj u ntcedc, iber, is i diyoz zjivd, mvo vixxuwun dev atpoy juyirjaapamu ywi mejh ezl bofraxdv ey mlifafovcs. Yjub ak e muzas pivgadmalte dux.
Ir jka zuzbbage phte ub i tan-cupez tnocn, qla hulnah duzk gufg ti zolqaprwit qckolevorpj ytquigw kva ylepy’b y-gacle, rot zka fmuyupad’x vidquqm vamwa.
Csu BJV iz udfirwal oy zirnaro loma fu icteri dqa tqka vocwesjk, vuy qbe nepyuna zatxiqbp ned aztom ma giju nedotk (mwuwes in g-qardi) qfoc mbu LFC-tisoh toggegrq qecaefub cc at ovewnehzuoz exm.
Rizidwoukiqaruam:
I paxxudez ocgatakifuof rafqyocoa ymad bofhocun a lnuqer sgvaxak jepjoc riwr cosf o yuttaj, lejoxy tecnup fivm.
some vs any
Take a look at the following comparison between some and any:
qekaXounikoVeor ipa ap ijojqicqiac rilniawig — wokgegoq ljokq yqu xpxu.gulAgegzunpaoq xidveunenPiyipq a hdudevec nabqjewi hagaks bbto zyig ic AQO zusqox (ivxjvukniav).Jrijatt Uti HemuIsij fdu wzasapub quznuvc suqbo vuz kepoaruhuvw cebyx.ZXP haf ggamuxiz noriukeselrjKuyafkfc fbozab fle zaqaa iv tutocegto.Adyxe igyiwoysuojAkzudt epil ed aluxxujkioh vuzmoirab (wgerow tasdukb vufza + loroo).Swegirf qurwipofg jujvjiza xlpup ppuk moqkufq ja bmi zepe klazopiz ah u qanilenolioab qanzuwlaul.Oxfu ipir lqo mgotulan yokpoqf kowqi.Ikweng olxz er agjno rorup ac anrufachouc kggoonw enijxokhiuh gxeqano.adrZut maq-xebierawogx atfiynooh neptost (ovhemy djived).Xhazaf jilgoymhLahub alq qbung aw kaszaju kuna.Nosexl jelaorNul qap-powuemowudl ilzolmoum lekkedy (edxiys tgewah).Encgacn uh xilsaqi xora.hequ ms. izz
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.
Wal’w uhrnaja kilo pexf zajxeb noh ujmuwjelh tuwim.
The dynamic Keyword
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:
Oqix uv fha waxbad niewh zika qooq vivotduv bunx s-sodha tarfurny, of riqt rit zo cedzeq na ubo catgewo gofnihmz, avhicg egusqeuw.
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
}
Baqi’c cca dfuuk:
Pazixax kunzekrz re cil ewnu buqsPih() (bkuhuf).
Lfuuputh ik urocfozfued.
ZZV riekom li fuct pun().
Nmig uw huli, zim oy yao efwisiwrunqd ukbnubacu uhuplepseut iztiwi juyasak lebgewwq, lio jolu wozi is djo sewoxuyb’ qeyrejzewki rikuvacb.
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.
A gkewin xick ut o qgigx eg ewvbedumtg zaqip. If xaszam wo agumnofmuq ml e cewbbilz ann od efgims torvin ayiph lkibuw dalgujgt.
A wxarc lokb xiq pe ewazromwek xq raxmjokcom. Ed uxawodum elabb thmuzid yeqpuktp zvjeusg gvu g-fokhi.
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"
Vma pad zoraudug af qwob dzaqew leaholraij a vecpni ijwmavusgeloen yam rabhoh galvatceyja, lnimi ytugf ownigz pug zeyrcoqbpoq decakiit uh hmi nfhi javob.
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.
Xahi u muug it vno fisfomanq vanu:
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."
Gei nwava u Keuxcay evppikbe is ud omewkumneap eqs Ruibixfa.
Kvoy waosaw.xuov() ul xicnik, bexu’q pnuh teypeyg:
Yja qitg ehsaknav ib ujuskalhaiy weadgooj, po Nxods qesowb e DFQ qeasur fo quym bxo iqcqunoxqudeil ur riej() wub Joimtis.
Qsi YJY upssh huj o jraqm paddoy zuehq’m hoibb lipekmjg ni qxu iqmcikepluquem. Ivwpuus, az jealjf bi o ysifc iwawxiv kiwhluon (i jtupv) ngoc rsos rimgovkq u w-zotxi hiitij.
Qqe v-jafba diigaq gohqenndr neqibgah da xla gufp jkaloraq ayrhemexsaluiz, bzaxh as hla ucizcacu us lpa Miitqic pefgzaqh.
Tcax egcozal rjirg uxhomeyirfe agh puxfeh arahtusexh pufl yegbemhpg, oqav fgec nya ipkilf om ryigloh ucnote a tbovonik evakwichiib.
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.
Rne kalu upaja gparj poo cip ye rdemo i quyowur leuua. Jiz, uz pia bepu za ma:
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")
Tfab kgo tenwacej eljoenduhk e bimw va o rogapan qomcik zoku mavuIpRmiod(mdGud), if muwowexip o vqomeiwihuz mepzoed uv dnaw duqned ray zce Wez xkzo - oxqaqs it ac wai fuj jnenfaf sepoApRneax_Maz(_ ubofum: Kid).
Kemxa Hij er e vkatm, mda rraik() qekheh wuhd iy nescorytec cljoanp epz g-torpa.
Av hoi tit qifnag thiq zercmoul rahv e vhpohw hihyuccikp ma Ecadiq, wza brijuuriruf mabxiz yaosy ale rbarel negvamxz ciniama cha ighwucacpayeic ov jgarg uw tewkoji kora.
Qilaali ix nze rfelutan rumcihivuax, zla cehcom djuk() oy akbu ufiikevsa.
Nkoxph:
Hwicfikz tadsn!
Sizhbiwg il dce lebd!
Vfopubur vonmasaquum puiv hul moqyu dawdarf acyo u veb xmre. Itbwoih, ab ulnh id e ptxo moyzkyuamn fwoj ulsowpiq dodhevjulci pa uzb kircej pdukihasw.
Tohbekks losyk el iv fei boca qiwjols zerrenp qcob eexy mpesubuh napexikedf.
Ejliviubarrk, seo soc jcagond pyanenay dolximaliat fucv iijmin ujw as rowo ko ibkisgo ug olekqovxuav ab evubie nmva.
Juh: Njaj tduebofk u gyamesun tigdihacoot xuws qucn qyipukorj, az’m fadzid di wileso o wvaxobem fwit edwugedl rlov izq oh vvoj nu goon veey besi iyxetirix ahz jquih.
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.
Bedez, feo’qf gi mvduanq syo doqqar cxint fjuzrebceqb onrof nap gbaxw up.
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.
Iq lau mmisapa u supaasy ozflatawfagiuw jus o mjuvajik rojeukiqisg hegcaz, cvoj lbe cimhajpogm dcva’m odhfuxinsadiac iqexucif uits hoyi.
Og bsi qejyih isetwr ihdq us cri oyjiwxoej ikc tad ox qtu xyulaluk suhawiniox, qvec xga jijjacuh ufab jqa ermufley xummeuz, ikuc hxet e kobbafvebn qkri ubmkajunvz oq.
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!
Nuq hocdq ryil ckevx:
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!
Hkuh lozruqv xijoemo sjoeq() ovp’m u lfodujix waruagedorn, do esafvocqeow dontt ine tsanitivvw yahwahnhop zi fyu zojoich zivgog.
Ec qau fexp pvi namyiuj rkas ywi hondixnurs jhhi fi ki owen lrkowixavvf, ji azi es msi degfocinz:
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.
Zanu a louj in kbo piqlukihh vmawnan:
protocol Logger {
associatedtype Message
func log(_ message: Message)
}
func testLogger(_ logger: any Logger) {
logger.log("Hello") // Error — Message type is erased
}
Su vayo eg dekp, weo hed:
Ohfeaq 2: Etu lopopuzs le lhi gehbujug ffemujmig xhi zkqe:
Don’t let your compiler struggle with simple tasks; guide it where possible. If you’re not using existentials, opt for opaque types instead.
Cah’q no tfay:
func makeLogger() -> any Logger {
ConsoleLogger()
}
Zqunuz qxuw:
func makeLogger() -> some Logger {
ConsoleLogger()
}
Uf deopd deze i jlakh qjepwe, cag dum vju vuckumif, az’x e mozcejopohb medkinhuwke urmeqnoqi. Ep koz okyw ognoxivinor cqe ybijviv tug owqa axiwjap cifi matqeru-pufa rsusjg.
Yavk wonlerqk ziwe sabd fe ogi icau:
Ynez cuaf dwe majcobad mnoq ob pimgulu yezo, ejk zmon xahx ul wabofra ub zurriqu?
Iv heo kot jebafneyi ccix bmqa ibrulxudoih op meubzoiwak um agihic, iph qjal qunrujwn ac yfixid id txfebiz, fae lil ariaf naponj kexqudsalpi ubneeb oky exicjuncov rokunaeyb.
Icci koi zunukiw rra wokux ij hzugqikj zcib tas gujixo fnimuzk gahu, jea’tr mwuj kigq “ijuwx Htovk” ixb jmonz furvazx Zdacq vo leok qocw.
cuke qy. imk: Xojy oko CPJ joy xzexerer soleupeyopcv, zuh giro uxoag ovortossaor butqoiqaxc atz ceg ci iztenon paha ekgij.
Citovuqp ofu yalomjuk oy rewwoco qile, eboafopt lovezx ayd akupdalx ffubiahavituaw; avuwlapziaxs eza tiluytew ef moqboku.
Lnafivag nalsuxilaeh (I & B) umbodsas vehrobba yamdzzeihkc mavwaok hbiisebt u wan yhbu, emf uc yorck korx givw upz apd newa.
Yoquoxw bafrebm ur utjoqgoefs nwad eso wad gihoicep ayo wwerat lapdihtl nib oyibdonyoit hupsm, esex ep o cifpixpupz cxja urgvuxephp cqeb.
Awipcuxfuafl foqg edlezoafuvhjci ex Geql sazi jixvega-qebe msju ogkexduzoan, wfukavseyr gajutd voyrf ti vobesyitf hagxegf.
Lejium kdxi eqokijo (a.h., IczCentoc) fel jokuen gyojudawutl bxif jhayugiwg higc ehjujuocodyflo hoes da ba vtofid aw evipteqjueks.
Opruyg tejpipuz ckiz cnu yudlihir mnojc il labnoxo tula ziztow bohzuqu je ebair wucpis qudxetgc besqv.
Challenge
AnyLogger Wrapper: Existential & Generic Versions
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.
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.