Protocols
Protocols define a blueprint of methods, properties, and other requirements that types can adopt to provide specific functionality. Any type that meets these requirements conforms to the protocol.
You can extend protocols to add default implementations or additional features that conforming types can use.
Protocol Syntax
Define a protocol like this:
protocol ExampleProtocol {
// Protocol requirements go here
}Types adopt protocols by listing them after their name, separated by colons. Multiple protocols are comma-separated:
struct ExampleStruct: FirstProtocol, SecondProtocol {
// Struct definition
}For classes with a superclass, list the superclass first:
class ExampleClass: BaseClass, FirstProtocol, SecondProtocol {
// Class definition
}Note: Protocol names start with a capital letter, like other Swift types (e.g.,
Int,String).
Property Requirements
Protocols can require instance or type properties with a specific name and type. They don't specify if it's stored or computed—only the name, type, and whether it's gettable or gettable/settable.
Use { get set } for gettable and settable properties, or { get } for gettable only. These are declared as variables with var.
For type properties, prefix with static in the protocol, even if implemented with class in classes.
Example protocol requiring a property:
protocol NamedEntity {
var name: String { get }
}A conforming struct:
struct Employee: NamedEntity {
var name: String
}
let alice = Employee(name: "Alice Smith")
// alice.name is "Alice Smith"A more complex class:
class Vehicle: NamedEntity {
var model: String
var manufacturer: String?
init(model: String, manufacturer: String? = nil) {
self.model = model
self.manufacturer = manufacturer
}
var name: String {
(manufacturer != nil ? manufacturer! + " " : "") + model
}
}
let car = Vehicle(model: "Sedan", manufacturer: "AutoCorp")
// car.name is "AutoCorp Sedan"Method Requirements
Protocols require instance or type methods without bodies. Use static for type methods.
Example:
protocol NumberGenerator {
func generate() -> Double
}Implementation with a simple random generator:
class SimpleGenerator: NumberGenerator {
var seed = 1.0
let multiplier = 16807.0
let modulus = 2147483647.0
func generate() -> Double {
seed = (seed * multiplier).truncatingRemainder(dividingBy: modulus)
return seed / modulus
}
}
let gen = SimpleGenerator()
print("Random: \(gen.generate())")
// Prints something like "Random: 0.000007822"Mutating Method Requirements
Use mutating for methods that modify the instance in value types (structs/enums).
Example:
protocol Switchable {
mutating func flip()
}Conforming enum:
enum LightState: Switchable {
case off, on
mutating func flip() {
self = (self == .off) ? .on : .off
}
}
var bulb = LightState.off
bulb.flip() // Now .onNote: Classes don't need
mutatingfor implementations.
Initializer Requirements
Protocols can require initializers without bodies.
protocol Configurable {
init(config: Int)
}In classes, mark as required:
class Device: Configurable {
required init(config: Int) {
// Setup
}
}Subclasses must also implement if overriding.
Failable Initializer Requirements
Supports failable (init?) or implicitly unwrapped failable (init!) initializers, satisfied by matching or nonfailable ones.
Protocols with Semantic Requirements Only
Some protocols define behaviors without methods/properties, like Sendable for concurrency-safe types. Swift standard library examples include Copyable, BitwiseCopyable.
Adopt them normally, even with empty bodies:
struct DataPoint: Copyable {
var value = 42
}
extension DataPoint: BitwiseCopyable { }Swift often infers these implicitly.
Protocols as Types
Use protocols in generics, as opaque types, or boxed types for runtime flexibility.
Delegation
Delegation lets a type hand off tasks to another via a protocol.
Example game with delegate for logging:
class CardGame {
let suits = 4
weak var delegate: GameLogger?
func play() {
delegate?.gameStarted(self)
// Game logic
delegate?.gameEnded(self)
}
protocol GameLogger: AnyObject {
func gameStarted(_ game: CardGame)
func gameEnded(_ game: CardGame)
}
}Logger implementation:
class BasicLogger: CardGame.GameLogger {
func gameStarted(_ game: CardGame) {
print("Game began")
}
func gameEnded(_ game: CardGame) {
print("Game over")
}
}Usage:
let logger = BasicLogger()
let game = CardGame()
game.delegate = logger
game.play()
// Prints "Game began" and "Game over"Adding Protocol Conformance with an Extension
Extend existing types to adopt protocols.
Example:
protocol Describable {
var description: String { get }
}
extension Vehicle: Describable {
var description: String {
"A vehicle: \(name)"
}
}Conditionally Conforming to a Protocol
Use where for constraints:
extension Array: Describable where Element: Describable {
var description: String {
map { $0.description }.joined(separator: ", ")
}
}Declaring Protocol Adoption with an Extension
If requirements are met, adopt with empty extension:
struct Animal {
var species: String
var description: String { "An animal: \(species)" }
}
extension Animal: Describable {}Adopting a Protocol Using a Synthesized Implementation
Swift synthesizes Equatable, Hashable, Comparable for simple types.
Example Equatable:
struct Point3D: Equatable {
var x = 0.0, y = 0.0, z = 0.0
}Hashable and Comparable similar.
Implicit Conformance to a Protocol
Swift infers Copyable, Sendable, etc. Suppress with ~:
struct Resource: ~Sendable {
let id: Int
}Or unavailable extension.
Collections of Protocol Types
Store in arrays:
let items: [Describable] = [car, Animal(species: "Cat")]
for item in items {
print(item.description)
}Protocol Inheritance
Inherit protocols:
protocol DetailedDescribable: Describable {
var detailedDescription: String { get }
}Class-Only Protocols
Use AnyObject:
protocol ClassSpecific: AnyObject {
// Definitions
}Protocol Composition
Combine with &:
protocol Aged {
var age: Int { get }
}
func greet(person: NamedEntity & Aged) {
print("Hello \(person.name), age \(person.age)")
}Include classes.
Checking for Protocol Conformance
Use is, as?, as!:
protocol Measurable {
var size: Double { get }
}
class Box: Measurable { var size: Double = 10 }
class Region: Measurable { var size: Double = 100 }
class Tool { var weight = 5 }
let things: [AnyObject] = [Box(), Region(), Tool()]
for thing in things {
if let measurable = thing as? Measurable {
print("Size: \(measurable.size)")
} else {
print("Not measurable")
}
}Optional Protocol Requirements
Use @objc and optional:
@objc protocol DataProvider {
@objc optional func getValue(for key: Int) -> Int
@objc optional var constantValue: Int { get }
}Use optional chaining.
Protocol Extensions
Provide defaults:
extension NumberGenerator {
func generateBool() -> Bool {
generate() > 0.5
}
}Providing Default Implementations
Defaults override optionals in calling.
Adding Constraints to Protocol Extensions
Use where:
extension Collection where Element: Equatable {
func allSame() -> Bool {
all { $0 == first }
}
}