Swift protocols define a contract of requirements (methods, properties, associated types) that any conforming type must satisfy; protocol extensions provide default implementations; generics parameterise functions and types over protocol-constrained types; some T is a compile-time opaque type with zero boxing cost; any T is a runtime existential with type-erasure boxing.
Inheritance says "a Dog IS-A Animal." Protocols say "a Dog CAN bark, CAN fetch." A struct can conform to multiple protocols; it cannot have multiple superclasses. Swift leans heavily on protocol conformance — even the standard library is built on protocols like Equatable, Comparable, Hashable, Codable, and Identifiable.
Protocols: conformance and default implementations
protocol Greetable {
var name: String { get } // required read-only property
func greet() -> String
}
// Protocol extension: default implementation
extension Greetable {
func greet() -> String {
return "Hello, I'm \(name)!"
}
}
struct Person: Greetable {
let name: String
// greet() provided by default — no need to implement
}
struct Robot: Greetable {
let name: String
func greet() -> String { // override the default
return "GREETINGS. I AM \(name.uppercased())."
}
}
// Protocol composition: &
func introduce(_ thing: Greetable & CustomStringConvertible) {
print(thing.greet())
print(thing.description)
}
let p = Person(name: "Ana")
print(p.greet()) // Hello, I'm Ana!
let r = Robot(name: "R2D2")
print(r.greet()) // GREETINGS. I AM R2D2.Generics
// Generic function: T is constrained to Comparable
func maximum<T: Comparable>(_ a: T, _ b: T) -> T {
return a > b ? a : b
}
print(maximum(3, 7)) // 7
print(maximum("apple", "banana")) // banana
// Generic struct
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { items.popLast() }
var top: Element? { items.last }
var isEmpty: Bool { items.isEmpty }
}
var stack = Stack<Int>()
stack.push(1); stack.push(2); stack.push(3)
print(stack.pop()!) // 3
// where clause: multiple constraints
func merge<T, U: Sequence>(_ dict: [T: [U.Element]], _ seq: U) -> [T: [U.Element]]
where U.Element: Hashable {
return dict // simplified — just shows constraint syntax
}Codable: automatic JSON serialisation
import Foundation
// Codable = Encodable & Decodable — synthesised automatically for simple structs
struct User: Codable {
let id: Int
let name: String
let email: String
let isAdmin: Bool
// Custom JSON keys via CodingKeys enum
enum CodingKeys: String, CodingKey {
case id, name, email
case isAdmin = "is_admin" // snake_case JSON -> camelCase Swift
}
}
// Encoding: Swift -> JSON
let user = User(id: 1, name: "Alice", email: "alice@example.com", isAdmin: false)
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try! encoder.encode(user)
print(String(data: jsonData, encoding: .utf8)!)
// Decoding: JSON -> Swift
let json = """{"id":2,"name":"Bob","email":"bob@example.com","is_admin":true}"""
let decoder = JSONDecoder()
let decoded = try! decoder.decode(User.self, from: json.data(using: .utf8)!)
print(decoded.name) // Bob
print(decoded.isAdmin) // truesome vs any: opaque types and existentials
some Protocol is an opaque type — the function returns a single specific type conforming to the protocol, but hides which type. The caller can't inspect it, but the compiler knows and optimises accordingly (zero boxing). any Protocol is an existential type — a type-erased box that can hold any conforming type at runtime, at the cost of a heap allocation for large types. SwiftUI uses some View everywhere; use any when you genuinely need a heterogeneous collection.
protocol Shape {
func area() -> Double
}
struct Circle: Shape {
var radius: Double
func area() -> Double { .pi * radius * radius }
}
struct Square: Shape {
var side: Double
func area() -> Double { side * side }
}
// some Shape: opaque — always returns the same (unknown to caller) concrete type
// Compiler can inline and optimise — zero overhead
func makeCircle(radius: Double) -> some Shape {
return Circle(radius: radius)
}
// any Shape: existential — can hold different types at runtime
// Small overhead (indirection) — but enables heterogeneous collections
func totalArea(_ shapes: [any Shape]) -> Double {
shapes.reduce(0) { $0 + $1.area() }
}
let shapes: [any Shape] = [Circle(radius: 5), Square(side: 3)]
print(totalArea(shapes)) // ~87.5struct Dog: Animal { ... }. You cannot retroactively say a type satisfies a protocol you wrote later — you need a extension Dog: NewProtocol { ... } extension. This makes the conformance graph explicit and searchable.protocol Collection { associatedtype Element } cannot be used as var things: Collection — the associated type is unresolved. Use any Collection (existential, Swift 5.7+), a generic constrained to Collection, or a concrete type conforming to Collection. This is why some Collection and generics exist.Protocol witness tables and the Swift ABI
When a type conforms to a protocol, the Swift compiler generates a protocol witness table (PWT) — a struct containing function pointers for each protocol requirement. When calling a protocol method through an existential (any Protocol), Swift uses the PWT for dynamic dispatch. Each type has its own PWT for each protocol it conforms to. For generics (func foo<T: Protocol>(x: T)), the compiler can specialise the function for each concrete type (like C++ template instantiation) or pass the PWT as an implicit argument — the latter is used for large generic functions to avoid code size explosion. The @_specialize attribute forces specialisation for specific types.
Primary associated types and typed throws (Swift 5.7/6.0)
Swift 5.7 introduced primary associated types — Collection<Int> is now valid syntax, constraining the element type. This enables some Collection<Int> as a function parameter without a full generic signature. Swift 6.0 introduced typed throws — func fetch() throws(NetworkError) -> Data declares the exact error type thrown. Callers don't need to catch arbitrary Error but can exhaustively switch on NetworkError. This closes a long-standing expressiveness gap compared to checked exceptions in Java (but without the overhead of checked exceptions).
Apple Inc. The Swift Programming Language. docs.swift.org/swift-book/. Chapters: Protocols, Generics. Swift Evolution: SE-0309 (unlock existentials), SE-0352 (any keyword), SE-0358 (primary associated types), SE-0413 (typed throws). Apple WWDC 2022: "Embrace Swift generics" and "Design protocol interfaces in Swift."