Lesson 79Protocols

Common Protocols

Swift Standard Library Protocols

These protocols are used throughout Swift and its standard library. Conforming to them unlocks powerful functionality!

Essential Protocols

Equatable

Compare with == and !=

Comparable

Compare with <, >, sort()

Hashable

Use in Set, Dictionary keys

Codable

JSON/Plist encoding

Identifiable

Unique identity for SwiftUI

CustomStringConvertible

Custom print() output

Collection Protocols

Sequence - for-in loop support
Collection - index-based access
MutableCollection - element modification
RandomAccessCollection - O(1) index access

Codable Deep Dive

Codable = Encodable & Decodable

  • Auto-synthesized for simple types
  • Use CodingKeys for custom JSON keys
  • Configure encoder/decoder for dates, etc.

Auto-Synthesis

Swift auto-generates conformance when all properties conform:

  • Equatable: All properties are Equatable
  • Hashable: All properties are Hashable
  • Codable: All properties are Codable
main.swift
// COMMON SWIFT PROTOCOLS
// Essential protocols you'll use frequently

// EQUATABLE - Compare for equality
struct Point: Equatable {
    var x: Int
    var y: Int
    // Swift auto-generates == for simple types
}

let p1 = Point(x: 1, y: 2)
let p2 = Point(x: 1, y: 2)
print(p1 == p2)  // true

// Custom Equatable
struct Person: Equatable {
    var id: Int
    var name: String

    static func == (lhs: Person, rhs: Person) -> Bool {
        return lhs.id == rhs.id  // Compare by ID only
    }
}

// COMPARABLE - Compare for ordering
struct Score: Comparable {
    var value: Int
    var player: String

    static func < (lhs: Score, rhs: Score) -> Bool {
        return lhs.value < rhs.value
    }
}

let scores = [Score(value: 100, player: "A"), Score(value: 50, player: "B")]
let sorted = scores.sorted()  // Uses <

// HASHABLE - Use in Sets and as Dictionary keys
struct User: Hashable {
    var id: Int
    var name: String

    func hash(into hasher: inout Hasher) {
        hasher.combine(id)  // Hash by ID
    }
}

var userSet: Set<User> = []
userSet.insert(User(id: 1, name: "Alice"))

var userScores: [User: Int] = [:]
userScores[User(id: 1, name: "Alice")] = 100

// CODABLE - JSON encoding/decoding
struct Article: Codable {
    var title: String
    var author: String
    var publishDate: Date
}

// Encode to JSON
let article = Article(title: "Swift", author: "Apple", publishDate: Date())
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
let jsonData = try? encoder.encode(article)

// Decode from JSON
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let json = """
{"title":"Swift","author":"Apple","publishDate":"2024-01-01T00:00:00Z"}
"""
if let data = json.data(using: .utf8) {
    let decoded = try? decoder.decode(Article.self, from: data)
}

// CUSTOM KEYS FOR CODABLE
struct Product: Codable {
    var productName: String
    var productPrice: Double

    enum CodingKeys: String, CodingKey {
        case productName = "name"
        case productPrice = "price"
    }
}

// IDENTIFIABLE - Unique identity (SwiftUI)
struct Task: Identifiable {
    let id = UUID()
    var title: String
    var isCompleted: Bool
}

// CUSTOMSTRINGCONVERTIBLE - String representation
struct Vector: CustomStringConvertible {
    var x: Double
    var y: Double

    var description: String {
        return "(\(x), \(y))"
    }
}

print(Vector(x: 3, y: 4))  // (3.0, 4.0)

// CUSTOMDEBUGSTRINGCONVERTIBLE - Debug output
struct Config: CustomDebugStringConvertible {
    var settings: [String: Any]

    var debugDescription: String {
        return "Config(\(settings.count) settings)"
    }
}

// SEQUENCE - For-in loop support
struct Countdown: Sequence {
    let start: Int

    func makeIterator() -> CountdownIterator {
        return CountdownIterator(count: start)
    }
}

struct CountdownIterator: IteratorProtocol {
    var count: Int

    mutating func next() -> Int? {
        if count <= 0 { return nil }
        defer { count -= 1 }
        return count
    }
}

for i in Countdown(start: 5) {
    print(i)  // 5, 4, 3, 2, 1
}

// COLLECTION - Array-like access
struct Stack<Element>: Collection {
    var items: [Element] = []

    var startIndex: Int { 0 }
    var endIndex: Int { items.count }

    subscript(index: Int) -> Element {
        return items[index]
    }

    func index(after i: Int) -> Int {
        return i + 1
    }
}

// EXPRESSIBLEBY... - Literal initialization
struct Meters: ExpressibleByIntegerLiteral {
    var value: Double

    init(integerLiteral value: Int) {
        self.value = Double(value)
    }
}

let distance: Meters = 100  // Creates Meters(value: 100.0)

Try It Yourself!

Create a Movie struct that conforms to Codable, Equatable, Comparable (by rating), and CustomStringConvertible!