keepcalculator

06 Numbers and Powers

Swift Protocols and Delegates: How They Work

Card No. 06·49685 · Updated · Standard formula, shown below

The calculator on this card is switched off while it is checked. The formula is below, and the worked example shows how to do the sum by hand.

Swift protocols define a blueprint of methods, properties, and other requirements that a class, struct, or enum can adopt, while delegates are a design pattern that lets one object hand off work to another. This article explains what protocols and delegates are, how they work together, and how to use them in your own Swift code—with concrete examples and common pitfalls to avoid.

What Are Protocols in Swift?

A protocol in Swift is like a contract. It declares a set of methods, properties, or other requirements that a type must implement if it adopts the protocol. For example, you can define a protocol that requires a method to calculate area, and then make several different types conform to it—each providing its own implementation.

Protocols can also require properties (with getter and setter specifications), initializers, and even associated types. They are a core part of Swift’s type system, enabling polymorphism and code reuse without inheritance.

Here’s a simple protocol definition:

protocol Shape {
    var area: Double { get }
    func draw()
}

Any type that conforms to Shape must have a computed or stored area property and a draw() method. You can then write functions that accept any Shape, making your code flexible and decoupled.

What Are Delegates in Swift?

Delegation is a design pattern where one object (the delegator) holds a weak reference to another object (the delegate) and calls methods on it when certain events occur. The delegate implements those methods to provide custom behavior. This is a common way to allow a view or controller to communicate with another object without tightly coupling them.

In Swift, delegation is typically implemented using a protocol. The delegator defines a protocol that the delegate must conform to. The delegator then calls the protocol methods, which the delegate implements.

For example, a NetworkManager might have a delegate that gets notified when data is received:

protocol NetworkManagerDelegate: AnyObject {
    func didReceiveData(_ data: Data)
    func didFailWithError(_ error: Error)
}

class NetworkManager {
    weak var delegate: NetworkManagerDelegate?
    // ...
}

Notice the AnyObject constraint and weak keyword—this prevents retain cycles. The delegate is typically a class (like a view controller) that conforms to the protocol and sets itself as the delegate.

How Protocols and Delegates Work Together

Protocols and delegates are complementary. The protocol defines the interface that the delegate must implement, and the delegator uses that interface to communicate. This allows the delegator to be completely unaware of the delegate’s concrete type, only that it conforms to the protocol.

Here’s a step-by-step of how it works:

  1. Define a protocol that lists the methods the delegate should implement.
  2. In the delegator class, add a property weak var delegate: SomeProtocol?.
  3. When an event occurs, call the delegate method (e.g., delegate?.didReceiveData(data)).
  4. The delegate conforms to the protocol and implements the methods.
  5. The delegate sets itself as the delegator’s delegate.

This pattern is used extensively in UIKit—for example, UITableViewDelegate and UITableViewDataSource are protocols that table views use to communicate with their delegate and data source.

How to Choose Between Delegates and Other Patterns

Delegates are ideal for one-to-one communication between objects, especially when you want to customize behavior without subclassing. However, they are not the only option. Here’s a quick comparison:

  • Delegates: One-to-one, good for callbacks and event handling. The delegator calls the delegate, and the delegate can return values or perform actions.
  • Closures: Often simpler for a single callback. You can pass a closure directly, avoiding the need to define a protocol.
  • Notifications: One-to-many, broadcast-style communication. Good for loosely coupled events (e.g., user logged in).
  • Combine / ObservableObject: For reactive streams and data binding, especially in SwiftUI.

Use delegates when you have multiple methods to call, need to maintain state, or want to keep a clear protocol interface. Use closures for simple one-off callbacks.

Common Problems and Misconceptions

One of the most common issues is retain cycles. If the delegate is a class and the delegator holds a strong reference to it, and the delegate holds a strong reference back to the delegator, neither will ever be deallocated. That’s why delegates are declared as weak and the protocol is constrained to AnyObject (class-only).

Another misconception is that delegates are only for view controllers. In reality, any object can use delegation—models, services, helpers—to communicate results.

Also, remember that protocol methods can be optional (using @objc and optional) only for Objective-C-compatible protocols. In pure Swift, you cannot mark methods as optional unless you use a default implementation in a protocol extension.

Protocol Extensions and Default Implementations

Protocol extensions allow you to provide default implementations for protocol methods. This is powerful because it lets you add behavior to a protocol without forcing every conforming type to implement it.

protocol Greeting {
    func greet()
}

extension Greeting {
    func greet() {
        print("Hello!")
    }
}

struct Person: Greeting {}
Person().greet() // prints "Hello!"

You can also add new methods in extensions that are not part of the protocol, but those are only available to types that are known to be the concrete type, not through the protocol interface.

Frequently Asked Questions

What is the difference between a delegate and a data source?

A delegate is responsible for handling events and user interactions (e.g., didSelectRowAt), while a data source provides the data that the view displays (e.g., numberOfRowsInSection). Both are often implemented by the same object, but they serve different purposes.

Can a delegate be a struct?

Technically yes, but it’s not recommended. Delegates are usually classes because they are passed by reference and can be weak. Structs are value types, so a weak reference to a struct is not allowed (unless it’s a class-constrained protocol). In practice, delegates are classes.

How do I make a delegate method optional?

In Swift, you can make a delegate method optional by using the @objc attribute and marking it with optional. For example: @objc protocol MyDelegate { @objc optional func optionalMethod() }. Alternatively, provide a default implementation in a protocol extension.

What is a retain cycle and how do delegates cause it?

A retain cycle happens when two objects hold strong references to each other, preventing deallocation. In delegation, if the delegator holds a strong reference to the delegate and the delegate holds a strong reference back, neither is released. Using weak for the delegate reference breaks the cycle.

Can I have multiple delegates?

No, a single delegator typically has one delegate. If you need multiple listeners, consider using notifications, Combine, or an array of delegates (though that’s less common).

Conclusion

Protocols and delegates are fundamental to Swift development, enabling clean, decoupled communication between objects. By understanding how they work and when to use them, you can write more modular and maintainable code. Practice with simple examples, and soon you’ll be using them naturally in your apps.

Related

Formula used

Each line is one input (in, sel, list) or one result (out: label, unit, formula, decimals). How the calculators work

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →