Lesson 76Protocols
Protocol Extensions
What are Protocol Extensions?
Protocol extensions let you provide default implementations for protocol requirements. This is the foundation of Protocol-Oriented Programming in Swift!
What They Can Do
Default Implementations
Provide default behavior for requirements
Add New Methods
Add functionality to all conforming types
Constrained Extensions
Add methods only when constraints are met
Extend Standard Library
Add to Collection, Sequence, etc.
Constrained Extensions
extension Collection where Element: Numeric {
var total: Element { ... }
}
var total: Element { ... }
}
Static vs Dynamic Dispatch
Important Gotcha!
- Methods in protocol = dynamic dispatch (overrides work)
- Methods only in extension = static dispatch (overrides ignored when using protocol type)
Benefits Over Inheritance
- Works with value types (structs, enums)
- Multiple protocol conformance
- More flexible composition
- Retroactive modeling
main.swift
// PROTOCOL EXTENSIONS
// Add default implementations to protocols
// BASIC PROTOCOL EXTENSION
protocol Greetable {
var name: String { get }
func greet() -> String
}
extension Greetable {
// Default implementation
func greet() -> String {
return "Hello, \(name)!"
}
// Additional functionality
func formalGreet() -> String {
return "Good day, \(name)."
}
}
struct Person: Greetable {
var name: String
// Uses default greet() implementation
}
struct Robot: Greetable {
var name: String
// Override default implementation
func greet() -> String {
return "Beep boop, I am \(name)"
}
}
let person = Person(name: "Alice")
let robot = Robot(name: "R2D2")
print(person.greet()) // Hello, Alice!
print(person.formalGreet()) // Good day, Alice.
print(robot.greet()) // Beep boop, I am R2D2
// EXTENDING STANDARD LIBRARY PROTOCOLS
extension Collection {
var isNotEmpty: Bool {
return !isEmpty
}
func printAll() {
for element in self {
print(element)
}
}
}
let numbers = [1, 2, 3]
print(numbers.isNotEmpty) // true
numbers.printAll() // 1, 2, 3
// CONSTRAINED PROTOCOL EXTENSIONS
extension Collection where Element: Numeric {
var total: Element {
return reduce(0, +)
}
}
print([1, 2, 3, 4, 5].total) // 15
print([1.5, 2.5, 3.0].total) // 7.0
// Only for Equatable elements
extension Collection where Element: Equatable {
func countOf(_ element: Element) -> Int {
return filter { $0 == element }.count
}
}
print([1, 2, 2, 3, 2].countOf(2)) // 3
// PROTOCOL-ORIENTED DESIGN
protocol Identifiable {
var id: String { get }
}
protocol Timestamped {
var createdAt: Date { get }
}
protocol Describable {
var description: String { get }
}
// Default implementations
extension Identifiable {
var id: String {
return UUID().uuidString
}
}
extension Describable where Self: Identifiable {
var description: String {
return "Object with ID: \(id)"
}
}
struct Article: Identifiable, Timestamped, Describable {
var title: String
var createdAt: Date = Date()
// Gets default id and description!
}
// ADDING FUNCTIONALITY TO EXISTING TYPES
extension Equatable {
func isEqual(to other: Self) -> Bool {
return self == other
}
}
print(5.isEqual(to: 5)) // true
print("hi".isEqual(to: "hello")) // false
// PROTOCOL EXTENSION VS CLASS INHERITANCE
// Protocols: Multiple conformance, value types supported
// Classes: Single inheritance, reference types only
protocol Flyable {
var maxAltitude: Int { get }
}
extension Flyable {
func fly() {
print("Flying up to \(maxAltitude) feet!")
}
}
protocol Swimmable {
var maxDepth: Int { get }
}
extension Swimmable {
func swim() {
print("Swimming down to \(maxDepth) feet!")
}
}
// Struct can conform to multiple protocols!
struct Duck: Flyable, Swimmable {
var maxAltitude: Int = 500
var maxDepth: Int = 10
}
let duck = Duck()
duck.fly() // Flying up to 500 feet!
duck.swim() // Swimming down to 10 feet!
// PROTOCOL EXTENSION GOTCHA
protocol Drawable {
func draw()
}
extension Drawable {
func draw() {
print("Default drawing")
}
func erase() {
print("Default erasing")
}
}
struct Circle: Drawable {
func draw() {
print("Drawing circle")
}
func erase() {
print("Erasing circle")
}
}
let shape: Drawable = Circle()
shape.draw() // Drawing circle (dispatched dynamically)
shape.erase() // Default erasing (static dispatch!)Try It Yourself!
Create a Stackable protocol with push/pop methods. Add a default peek() implementation via protocol extension!