Swift: Swift Protocols

Protocols define a blueprint of methods, properties, and other requirements. They are the cornerstone of Swift's Protocol-Oriented Programming (POP). This lesson covers how to define and conform to protocols, plus the delegate pattern in practice.

1. What You'll Learn


2. A Real-World Payment Developer Story

(1) Pain Point: Each Payment Method Has Its Own Interface

Alice's team needs to integrate multiple payment methods — credit card, PayPal, WeChat Pay:

SWIFT
class CreditCardPayment {
    func processCardPayment(amount: Double, cardNumber: String) -> Bool {
        // Credit card processing
        return true
    }
}
class PayPalPayment {
    func payWithPayPal(amount: Double, email: String) -> Bool {
        // PayPal processing
        return true
    }
}
class WeChatPayment {
    func wechatPay(amount: Double, code: String) -> Bool {
        // WeChat Pay processing
        return true
    }
}

Three classes, three different method names, three different parameter lists. Business logic is littered with if-else checks for the payment type. Adding one more payment method means changing every call site.

(2) Solution: Protocols

SWIFT
protocol PaymentMethod {
    func pay(amount: Double) -> Bool
}
struct CreditCard: PaymentMethod {
    let cardNumber: String
    func pay(amount: Double) -> Bool { /* Process */ true }
}
struct PayPal: PaymentMethod {
    let email: String
    func pay(amount: Double) -> Bool { /* Process */ true }
}
// Unified handling
func checkout(amount: Double, using method: PaymentMethod) {
    method.pay(amount: amount)
}

Don't care what payment method it is — only that it conforms to PaymentMethod.

(3) Benefit: Unified Interface, Extensible

Dimension Independent Implementations Protocol Unification
Method names All different Unified pay(amount:)
Adding a method Change all call sites Only add a conforming type
Testing Write a test suite for each Protocol mock for unified testing
Coupling High Low (Protocol-Oriented Programming)

3. Defining and Conforming to Protocols

(1) Protocol Syntax

100%
graph TB
    A[protocol keyword] --> B[Protocol name]
    B --> C[Property requirements]
    B --> D[Method requirements]
    B --> E[Subscript requirements]
    C --> F["var name: String { get set }"]
    D --> G["func work()"]
    E --> H["subscript(...) -> Type"]
Protocol Requirement Syntax Notes
Read-write property { get set } var or computed property (get+set)
Read-only property { get } let, var, or read-only computed property
Instance method func name() Signature only, no implementation
mutating method mutating func name() Value types can modify self

▶ Example: Defining and Conforming to Protocols

SWIFT
// ============================================
// Defining and conforming to a protocol
// ============================================
// 1. Define the protocol
protocol Describable {
    var description: String { get }
    func summarize() -> String
}
// 2. Struct conforming to the protocol
struct Book: Describable {
    let title: String
    let author: String
    var description: String {
        return "\"\(title)\" by \(author)"
    }
    func summarize() -> String {
        return "Book: \(description)"
    }
}
// 3. Class conforming to the protocol
class Movie: Describable {
    let title: String
    let director: String
    init(title: String, director: String) {
        self.title = title
        self.director = director
    }
    var description: String {
        return "\(title) (directed by \(director))"
    }
    func summarize() -> String {
        return "Movie: \(description)"
    }
}
let book = Book(title: "1984", author: "George Orwell")
let movie = Movie(title: "Inception", director: "Christopher Nolan")
print(book.summarize())
print(movie.summarize())

Output:

TEXT 📖 Display only
Book: "1984" by George Orwell
Movie: Inception (directed by Christopher Nolan)

4. Protocol Inheritance and Composition

(1) Protocol Inheritance

A protocol can inherit from one or more other protocols:

100%
graph TB
    A[Protocol: Payable] --> B["Property: amount"]
    A --> C["Method: process()"]
    B --> D[Protocol: Refundable]
    C --> D
    D --> E["Method: refund()"]
    D --> F[Type: CreditCard]
    F --> G["Implements Payable + Refundable"]

▶ Example: Protocol Inheritance

SWIFT
// ============================================
// Protocol inheritance — a refundable payment protocol
// ============================================
protocol Payable {
    var amount: Double { get }
    func process() -> Bool
}
protocol Refundable: Payable {  // Inherits Payable
    func refund() -> Bool
}
// CreditCard implements refundable payment
struct CreditCard: Refundable {
    let amount: Double
    let cardNumber: String
    func process() -> Bool {
        print("Processing $\(amount) on card \(cardNumber)")
        return true
    }
    func refund() -> Bool {
        print("Refunding $\(amount) to card \(cardNumber)")
        return true
    }
}
// GiftCard can pay but not refund
struct GiftCard: Payable {
    let amount: Double
    let code: String
    func process() -> Bool {
        print("Processing $\(amount) with gift card \(code)")
        return true
    }
}
let payments: [Payable] = [
    CreditCard(amount: 100, cardNumber: "1234"),
    GiftCard(amount: 50, code: "GIFT-001")
]
for payment in payments {
    payment.process()
    // Check if also Refundable
    if let refundable = payment as? Refundable {
        print("  This payment can be refunded")
    }
}

Output:

TEXT 📖 Display only
Processing $100.0 on card 1234
  This payment can be refunded
Processing $50.0 with gift card GIFT-001

(2) Protocol Composition

Use the & operator to combine multiple protocols, requiring a type to conform to all of them simultaneously:

SWIFT
protocol Identifiable {
    var id: String { get }
}
protocol Loggable {
    func log()
}
// Type must conform to both Identifiable and Loggable
func saveItem(_ item: Identifiable & Loggable) {
    print("Saving item \(item.id)")
    item.log()
}
struct User: Identifiable, Loggable {
    let id: String
    func log() { print("User logged: \(id)") }
}
let user = User(id: "U-001")
saveItem(user)

Output:

TEXT 📖 Display only
Saving item U-001
User logged: U-001

5. Default Implementations via Extension and the Delegate Pattern

(1) Providing Default Implementations with extension

Use extension to provide default implementations for protocol methods — conforming types can choose not to implement them:

SWIFT
// ============================================
// Adding default implementations to protocols via extension
// ============================================
protocol Greetable {
    var name: String { get }
    func greet() -> String
}
// Default implementation
extension Greetable {
    func greet() -> String {
        return "Hello, \(name)!"
    }
}
// Person uses the default implementation
struct Person: Greetable {
    let name: String
    // No need to implement greet() — uses the default version
}
// Robot provides a custom implementation
struct Robot: Greetable {
    let name: String
    func greet() -> String {
        return "Beep boop, I am \(name)"
    }
}
print(Person(name: "Alice").greet())
print(Robot(name: "R2-D2").greet())

Output:

TEXT 📖 Display only
Hello, Alice!
Beep boop, I am R2-D2

(2) The Delegate Pattern

The delegate pattern is a design pattern where one object delegates part of its work to another object that conforms to a protocol:

100%
sequenceDiagram
    participant A as Class A
    participant D as Delegate (protocol)
    participant B as Class B
    A->>D: An event occurred
    D->>B: Calls delegate method
    B-->>A: Returns processing result

▶ Example: Implementing the Delegate Pattern

SWIFT
// ============================================
// Delegate pattern: Download manager
// ============================================
// 1. Define the delegate protocol
protocol DownloadDelegate: AnyObject {
    func downloadDidStart(_ url: String)
    func downloadDidProgress(_ url: String, percent: Double)
    func downloadDidComplete(_ url: String, data: String)
    func downloadDidFail(_ url: String, error: String)
}
// 2. Download manager — delegates events to an external handler
class DownloadManager {
    weak var delegate: DownloadDelegate?
    func download(from url: String) {
        delegate?.downloadDidStart(url)
        // Simulate download progress
        for i in 1...5 {
            let percent = Double(i) / 5.0 * 100
            delegate?.downloadDidProgress(url, percent: percent)
        }
        // Simulate completion
        delegate?.downloadDidComplete(url, data: "Downloaded content from \(url)")
    }
}
// 3. View controller — acting as the delegate
class ViewController: DownloadDelegate {
    func downloadDidStart(_ url: String) {
        print("[UI] Download started: \(url)")
    }
    func downloadDidProgress(_ url: String, percent: Double) {
        print("[UI] \(url): \(Int(percent))%")
    }
    func downloadDidComplete(_ url: String, data: String) {
        print("[UI] Download complete: \(data.prefix(20))...")
    }
    func downloadDidFail(_ url: String, error: String) {
        print("[UI] Download failed: \(error)")
    }
}
let manager = DownloadManager()
let ui = ViewController()
manager.delegate = ui  // Set the delegate
manager.download(from: "https://example.com/file.zip")

Output:

TEXT 📖 Display only
[UI] Download started: https://example.com/file.zip
[UI] https://example.com/file.zip: 20%
[UI] https://example.com/file.zip: 40%
[UI] https://example.com/file.zip: 60%
[UI] https://example.com/file.zip: 80%
[UI] https://example.com/file.zip: 100%
[UI] Download complete: Downloaded content...

Tip: The AnyObject constraint requires the delegate to be a class type (not a struct), allowing the use of weak var to avoid retain cycles. This is standard practice for the delegate pattern.


6. Complete Example: Configurable Data Validator

SWIFT
// ============================================
// Complete example: Data validation system
// Features: Protocol + protocol inheritance + extension defaults + delegate pattern
// ============================================
import Foundation
// 1. Validation protocol hierarchy
protocol Validatable {
    var value: Any { get }
    func validate() -> Bool
}
// Validation with detailed error reporting
protocol DetailedValidatable: Validatable {
    func errorMessage() -> String
}
// Default implementation
extension Validatable {
    func validate() -> Bool { return true }
}
// 2. Concrete validators
struct EmailValidator: DetailedValidatable {
    let value: Any
    func validate() -> Bool {
        guard let email = value as? String else { return false }
        return email.contains("@") && email.contains(".")
    }
    func errorMessage() -> String {
        return "Invalid email format"
    }
}
struct AgeValidator: DetailedValidatable {
    let value: Any
    func validate() -> Bool {
        guard let age = value as? Int else { return false }
        return age >= 18 && age <= 120
    }
    func errorMessage() -> String {
        return "Age must be between 18 and 120"
    }
}
struct NonEmptyValidator: Validatable {
    let value: Any
    // Uses default validate() which returns true, but we customize it
    func validate() -> Bool {
        guard let text = value as? String else { return false }
        return !text.isEmpty
    }
}
// 3. Delegate — validation result handler
protocol ValidationDelegate: AnyObject {
    func validationDidSucceed(for field: String)
    func validationDidFail(for field: String, error: String)
}
// 4. Validation manager
class ValidationManager {
    weak var delegate: ValidationDelegate?
    private var validators: [(field: String, validator: Validatable)] = []
    func addValidator(for field: String, validator: Validatable) {
        validators.append((field, validator))
    }
    func runAll() -> Bool {
        var allValid = true
        for (field, validator) in validators {
            if validator.validate() {
                delegate?.validationDidSucceed(for: field)
            } else {
                allValid = false
                let error = (validator as? DetailedValidatable)?.errorMessage() ?? "Validation failed"
                delegate?.validationDidFail(for: field, error: error)
            }
        }
        return allValid
    }
}
// 5. Usage
class FormController: ValidationDelegate {
    func validationDidSucceed(for field: String) {
        print("[OK] \(field) is valid")
    }
    func validationDidFail(for field: String, error: String) {
        print("[FAIL] \(field): \(error)")
    }
}
let manager = ValidationManager()
manager.delegate = FormController()
manager.addValidator(for: "email", validator: EmailValidator(value: "alice@example.com"))
manager.addValidator(for: "age", validator: AgeValidator(value: 25))
manager.addValidator(for: "name", validator: NonEmptyValidator(value: "Alice"))
let allValid = manager.runAll()
print("Form valid: \(allValid)")

Output:

TEXT 📖 Display only
[OK] email is valid
[OK] age is valid
[OK] name is valid
Form valid: true

❓ FAQ

Q What's the difference between a protocol and a base class?
A A protocol only defines an interface blueprint — it can't store properties (only declare { get set }). A base class can provide stored properties and method implementations. A class can conform to multiple protocols but can only inherit from one base class. Protocol-Oriented Programming (POP) is Swift's core design philosophy.
Q When to use protocol default implementations vs a base class?
A Use a base class when you need shared stored properties or inherited initializers. Use protocol extensions when you only need to define a behavioral interface with default implementations. Prefer protocols because they are more flexible (multiple protocol conformance).
Q Why use weak var for delegates?
A To avoid retain cycles. DownloadManager holds delegate, and if delegate also holds DownloadManager, a strong reference cycle forms. weak prevents the delegate reference from increasing the reference count. Requiring the delegate to be a class ensures weak can be used, since structs don't support weak references.
Q What's the purpose of mutating methods in protocols?
A It marks a method that can modify the conforming type's own properties. Struct and enum methods cannot modify properties by default — adding mutating allows it. Class implementations don't need to write mutating — class methods can modify properties by default.
Q What are @objc protocols used for?
A @objc protocols allow the protocol to be used by Objective-C code, commonly seen in UIKit delegate protocols. @objc protocols can have optional requirements (methods that are optional to implement) — a feature pure Swift protocols don't have.

📖 Summary


📝 Exercises

  1. Basic: Define a Flyable protocol with a fly() method. Create a Bird struct and an Airplane class that conform to it, each implementing fly().
  2. Intermediate: Define an Encryptable protocol (func encrypt(_: String) -> String) and a Decryptable protocol (func decrypt(_: String) -> String). Have a CaesarCipher class conform to both (Caesar cipher, shift of 3). Use protocol composition Encryptable & Decryptable as the parameter type.
  3. Challenge: Implement a "Sortable Data Source" delegate pattern: define a SortableDataSource protocol (func numberOfItems() -> Int and func item(at index: Int) -> String), and a SortDelegate protocol (func didSort(items: [String])). Create a NameList class conforming to SortableDataSource and a TableView class conforming to SortDelegate. NameList notifies TableView after sorting.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏