Protocols
|
This section documents the Swift 6 language mode as shipped by Swift 6.3, as published in The Swift Programming Language at docs.swift.org, which is the reference these pages are written and verified against. 6.4-beta-only features are always flagged as such — never presented as baseline. This content was generated with the assistance of AI and should be verified against docs.swift.org before being relied on in production. This section’s bibliography lists the reference material consulted while preparing these pages. |
A protocol defines a blueprint of properties, methods, subscripts and other requirements that suit a particular task or piece of functionality — adopted by a class, structure, or enumeration to provide an actual implementation of those requirements. Protocols are what makes Swift’s protocol-oriented style of design possible: composing behavior by adopting protocols instead of, or in addition to, subclassing (see Inheritance).
Protocol Syntax and Requirements
protocol FullyNamed {
var fullName: String { get } // property requirement: readable, `get`-only here
}
protocol Container {
associatedtype Item
var count: Int { get }
mutating func append(_ item: Item) // `mutating` is required on any method that may need it,
subscript(index: Int) -> Item { get } // even though a class conformer will ignore the keyword
init(item: Item) // initializer requirement
}
struct Person: FullyNamed {
var fullName: String
}
A protocol declares requirements but provides no implementation of its own: a property requirement names a type
and whether it must be gettable ({ get }) or gettable-and-settable ({ get set }), never var/let; a method
requirement omits a body; a mutating method requirement lets a value-type conformer mark its implementation
mutating (a class conformer simply omits mutating, since reference types don’t need it); a subscript
requirement is written like a computed property’s; and an initializer requirement forces every non-final
conforming class to mark its matching initializer required (see
Initialization and Deinitialization),
so that it — and every subclass — is guaranteed to provide it.
Protocols with Only Semantic Requirements
protocol Togglable {
mutating func toggle() // the *signature* is the only formal requirement...
}
// ...but Togglable's documentation states the *semantic* requirement that toggle() must alternate between
// exactly two states -- nothing in the type system enforces that meaning, only convention and documentation do.
enum OnOffSwitch: Togglable {
case off, on
mutating func toggle() {
self = self == .off ? .on : .off
}
}
Not every protocol requirement can be captured in Swift’s type system: Togglable above formally requires only a
method with a matching signature, but its real contract — "toggling flips between exactly two states" — is a
semantic requirement, documented in prose and trusted rather than compiler-checked. Many standard-library
protocols (Equatable, Hashable, Comparable) work the same way: they name axioms (Equatable’s `== must be
reflexive, symmetric and transitive) that a conformer is expected to honor even though nothing stops a broken
implementation from compiling.
Protocols as Types, any, and Delegation
protocol RandomNumberGenerator {
func random() -> Double
}
struct LinearCongruentialGenerator: RandomNumberGenerator {
func random() -> Double { 0.42 } // a fixed stand-in for a real generator
}
func rollDice(using generator: some RandomNumberGenerator) -> Int { // a concrete, opaque conforming type
Int(generator.random() * 6) + 1
}
var generators: [any RandomNumberGenerator] = [LinearCongruentialGenerator()] // `any`: a boxed existential
protocol DiceGameDelegate: AnyObject { // a delegate protocol, typically class-only (see below)
func gameDidStart()
func gameDidEnd()
}
final class DiceGame {
weak var delegate: DiceGameDelegate? // `weak` avoids a reference cycle -- see Automatic Reference Counting
func play() {
delegate?.gameDidStart()
// ... play the game ...
delegate?.gameDidEnd()
}
}
A protocol name can be used as a type in its own right, wherever a type is expected. Written directly
(some RandomNumberGenerator on a parameter, or as a generic constraint) it names a specific, statically-known
conforming type the compiler still tracks; written as any RandomNumberGenerator, it erases that specific type
into a boxed existential value that can hold any conformer, at the cost of dynamic dispatch and, for a
non-class-bound protocol, potential heap allocation — any is required whenever a protocol type is stored,
returned, or otherwise needs to abstract over which concrete type is actually inside (see
Opaque and Boxed Protocol Types for the
full some vs. any story). Delegation is a design pattern built directly on this: a type (DiceGame) hands
off part of its responsibility to an external object conforming to a delegate protocol, held as a weak reference
to avoid the strong reference cycle a delegate relationship would otherwise create.
Conformance via Extensions and Conditional Conformance
protocol TextRepresentable {
var textualDescription: String { get }
}
struct Hamster {
var name: String
}
extension Hamster: TextRepresentable { // conformance declared separately from the type itself
var textualDescription: String { "A hamster named \(name)" }
}
struct Stack<Element> {
var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element { items.removeLast() }
}
extension Stack: TextRepresentable where Element: TextRepresentable { // *conditional* conformance
var textualDescription: String {
"[" + items.map(\.textualDescription).joined(separator: ", ") + "]"
}
}
A type conforms to a protocol either at its own declaration or later via an extension — either way, once the
conformance exists, instances of the type can be used anywhere that protocol is required. A generic type can
conform to a protocol conditionally, only when its type parameter itself satisfies some requirement (where
Element: TextRepresentable above): Stack<Hamster> conforms to TextRepresentable, but Stack<Int> does not,
since Int doesn’t.
Synthesized Equatable/Hashable/Comparable/Codable
struct Position: Equatable, Hashable, Comparable { // every requirement synthesized -- no code needed below
let file: String
let line: Int
static func < (lhs: Position, rhs: Position) -> Bool { // Comparable still needs `<` written by hand
(lhs.file, lhs.line) < (rhs.file, rhs.line)
}
}
struct Coordinate: Codable { // Encodable + Decodable, fully synthesized
let latitude: Double
let longitude: Double
}
The compiler synthesizes == and hash(into:) for free for a struct or enum whose stored properties (or
associated values) are all themselves Equatable/Hashable, so long as the conformance is declared with no
custom implementation of the requirement it synthesizes; Comparable synthesis only covers ordering < for a
subset of cases in specific situations (a raw-value or no-payload enum), so Position above still writes <
itself. Codable (Encodable & Decodable) is synthesized whenever every stored property is itself Codable,
producing member-by-member encoding/decoding with no code written at all — see
Codable and Serialization for customizing it.
Implicit Conformance and Suppressing It (~Copyable, ~Escapable)
struct Ordinary {} // implicitly Copyable and Escapable -- true of almost every type
struct FileHandle: ~Copyable { // suppresses the implicit Copyable conformance (see Structures and Classes)
let descriptor: Int32
}
struct BufferView<T>: ~Escapable { // suppresses Escapable: a value that must not outlive what it borrows from
let pointer: UnsafePointer<T>
}
Every Swift type is implicitly Copyable (it may be copied freely) and Escapable (an instance may outlive the
scope it was created in) unless it opts out. ~Copyable and ~Escapable suppress those two implicit
conformances specifically — they are not ordinary protocols a type opts into, but markers removing a default
every type otherwise gets, used for types whose whole point is a single, non-duplicable owner (a file handle, a
lock) or a value tied to some other value’s lifetime (a buffer view over borrowed memory). See
Structures and Classes for ~Copyable in full and
Memory Safety and Unsafe Pointers for
`~Escapable’s lifetime-dependency use cases.
Collections of Protocol Types
let things: [TextRepresentable] = [Hamster(name: "Fluffy"), Stack<Hamster>()] // a homogeneous array of a protocol type
for thing in things {
print(thing.textualDescription) // dispatches to each element's own conformance, not a common base class
}
An array (or any collection) typed as [SomeProtocol] can hold instances of any mix of conforming types — structs, classes, and enums together — with no shared base class required at all, which is exactly the
composition-friendly alternative to a class hierarchy discussed in
Inheritance.
Protocol Inheritance, Class-Only Protocols, and Composition
protocol Named {
var name: String { get }
}
protocol Aged {
var age: Int { get }
}
protocol PrestigiousSociety: Named, Aged {} // protocol inheritance: requires everything Named and Aged require
protocol Cloneable: AnyObject { // class-only: only a class may conform
func clone() -> Self
}
func describe(_ value: Named & Aged) -> String { // protocol composition: BOTH sets of requirements, inline
"\(value.name) is \(value.age)"
}
A protocol can inherit from one or more other protocols with the same : syntax a class uses, adding every
inherited requirement to its own; a protocol restricted with : AnyObject may only be adopted by a class, useful
whenever conforming code needs reference semantics (an identity check, a weak reference) rather than a value
type. SomeProtocol & OtherProtocol — protocol composition — names, inline, any type conforming to both at
once without declaring a new named protocol for the combination, as `describe’s parameter type does above.
Checking Conformance with is / as?
let candidate: Any = Hamster(name: "Fluffy")
if candidate is TextRepresentable { // `is`: does this value conform?
print("conforms")
}
if let describable = candidate as? TextRepresentable { // `as?`: conditionally cast to the protocol type
print(describable.textualDescription)
}
is asks a yes/no question about whether a value’s dynamic type conforms to a given protocol (or is a given
class, or subclass); as? performs the same check and, on success, produces the value typed as the protocol
(or class) itself, nil otherwise — the same two operators used for ordinary class-hierarchy casts, covered in
full in Type Casting and Reflection.
@objc optional Requirements
import Foundation
@objc protocol CounterDataSource {
@objc optional func increment(forCount count: Int) -> Int // may be left unimplemented by a conformer
@objc optional var fixedIncrement: Int { get }
}
class ThreeSource: NSObject, CounterDataSource {
let fixedIncrement = 3 // implements only one of the two optional requirements
}
optional requirements exist only inside an @objc protocol (which in turn means only a class — ultimately one
rooted in NSObject — can conform), and let a conformer skip some requirements entirely; calling an optional
requirement always goes through optional chaining (someDataSource.increment?(forCount: 4)) or an explicit
responds(to:) check, since the compiler cannot guarantee it was implemented. This exists specifically for
interoperating with Objective-C APIs that predate Swift’s own preferred alternative — a protocol extension
supplying a default implementation, covered next — and is unavailable to a pure-Swift protocol.
Protocol Extensions with Default Implementations and Constraints
protocol PrettyTextRepresentable: TextRepresentable {
var prettyTextualDescription: String { get }
}
extension PrettyTextRepresentable {
var prettyTextualDescription: String { // a default implementation every conformer gets
"✨ " + textualDescription + " ✨" // free, in terms of the *other* requirement
}
}
extension Collection where Element: Equatable { // an extension constrained to a subset of conformers
func allEqual() -> Bool {
guard let first else { return true }
return allSatisfy { $0 == first }
}
}
A protocol extension can supply a default implementation for one of its own requirements (or for a member the
protocol doesn’t even require), which every conforming type gets automatically unless it provides its own,
more specific implementation instead — Swift always calls the more specific one, whichever is available. A
where clause on the extension (as on Collection above) further restricts the default to only those
conformers satisfying an extra constraint, letting a single protocol’s extensions layer general-purpose behavior
and behavior specific to certain kinds of conformers side by side.
Protocol-Oriented Design: the Standard Library as a Case Study
The standard library’s own collection types are the canonical example of protocol-oriented design: Sequence,
Collection, BidirectionalCollection and RandomAccessCollection form a protocol hierarchy where each level
adds requirements (and, via extensions, dozens of default-implemented methods like map, filter, and
allEqual above) that every conformer — Array, Set, Dictionary, a custom type entirely — gets without
writing them itself, exactly the mechanism shown for PrettyTextRepresentable above (see
Collections for the hierarchy itself). This is the practical
payoff of preferring protocols over a class hierarchy for shared behavior: a struct (which cannot subclass
anything) can still adopt Collection and immediately gain the same rich, tested behavior a class-based design
would have needed a common superclass to share — with no forced "is a" relationship, no single-inheritance
ceiling, and conformance completely decoupled from a type’s storage strategy.
See Also
-
Inheritance — subclassing, and why composing protocols is often the better alternative.
-
Generics — associated types,
some/any, and genericwhereclauses in full. -
Opaque and Boxed Protocol Types —
somevs.anyin depth. -
Type Casting and Reflection —
is/as?/as!across classes and protocols alike. -
Codable and Serialization — customizing the synthesized
Codableconformance. -
Collections —
Sequence/Collectionas the flagship protocol-oriented design.