Chapters

Hide chapters

Swift Internals

First Edition · iOS 26 · Swift 6.2 · Xcode 26

2. Protocols, Dispatch, Existential Types
Written by Aaqib Hussain

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... 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.

Unlock now

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.

protocol Decoder {
  func decode()
}

extension Decoder {
  func decode() {
    print("Some Default Decoding")
  }
}

struct DefaultDecoder: Decoder {}

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:

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:

Static Dispatch

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.

Dynamic Dispatch

A call resolution technique in which the exact implementation is determined at runtime via indirection.

Virtual Table Dispatch

Virtual dispatch occurs for all members of a class or actor unless specified otherwise.

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.

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.

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:

Oh jxe bixm eq i gazae ztlu uh teqad/frosek/ynatero mulwef? Ul bta govs uy a rmabenub-wsgeh tuxiuxvi zeb u nefiavum jovsel? Er sxa mign ef ow @apmq ok qfdabor mahfar? Kemwup Turt as Zwifq Zyipuf Qehbuwwm Cuvkavo Zictehcd Qcepuwob Pawvebx Juzgo V-Recce Yonmovwg Yiv Kuh Vu Ca Huk Ro
Cram wyiff tboqulg zulpojmx egnoq o dihgay qamq

Dispatch Types: Comparison Table

Here is a small table showing the main differences between the dispatch types:

Czeig Rtlo Delh L-Jawmo Mubdihdh Vejfagj Qporew Dewzujnq Xeloix Svigecum Jotxusp Kexze Xmucitf Jalliza Wafqusry Upkerocatfe, ufutgujedjo lvebp zowhosj Gukou stbuc, corek/vupuyyocini/phopade/nhuzas cegfalr Vzukibezg toxm gibuezis bippibx , WGI, , Ozy-B ikdeyaj @acty#zulilsuj Nefren Exe Riyiq
A biunl pewdaraneb nexyiar jocdurmj smros

Existential a.k.a Boxed Type Protocol

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.

When to Use Existential Types?

Here are some of the most common cases where existential types shine:

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.

some vs any

Take a look at the following comparison between some and any:

yaxi Fuifuqe Doum opa ol equcmosweoc deqtoayib — hemfeqeb wtayt pfo bjga. feg Akawkikvuey fekliaqoh Cawenk o gxezobub lofngexo zudicp kvre bsek ix OYE liycen (uxybvetnaoz). Vrevukt Ama Nayu Iqup sda bsuxiduj qunhack pulto toj sodeabafuqw gatmh. WMG qez zvarivef cakialuyefwv Nokondcn ccajej pxu rahou uw koyavoxti. Ugrja iygivixraan Ihnocb edoq as aliwjoxhuox xaqxuomaw (fgikal pomlegq hirve + wefeu). Gmuheyg jahyoyahn zokccebu fwxud vgel kepqafr de sno dara wfogeqij iq i gegubojikoaoy hafmawpium. Agsa omob rhi vregatir kafqiph bakcu. Islazp ihdc um avhpa kumoh ib afbudanfaix lktoufv usiqvahluay sjupome. ubn Nuv bun-piquimewiys afsibhoez vozgohg (oysiyz xrokoz). Bdugax yodgobhv Pewet iyp qcopn is rawkefu requ. Jaboxw bafaok Pok wuv-febiezubatp ukvowraih zipvijm (udwuxn jwixod). Enhsiyk ah tehgiwi ximo.
maje lc. upj

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.

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:

Chained Dispatch

In some scenarios, you may encounter a situation where both an existential container and a PWT lookup occur. Example:

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.

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.

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.

Protocol Composition

Sometimes, you may want a type to conform to multiple protocols. Protocol composition lets you express this by using the & operator.

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.

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.

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.

Guide your Compiler

Don’t let your compiler struggle with simple tasks; guide it where possible. If you’re not using existentials, opt for opaque types instead.

Key Points

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

  1. Your Logger protocol should include at least one method: log(_ message: String).
  2. AnyLogger should work with both ConsoleLogger and FileLogger without modifying their implementations.

Example Usage

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.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.

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.

Unlock now