Swift protocols define a blueprint of methods, properties, and other requirements that a type must implement, while delegates are a design pattern that uses protocols to enable one object to communicate back to another. This guide explains how to define and conform to protocols, how the delegate pattern works, and when to use it—with concrete Swift examples you can adapt.
What Are Protocols in Swift?
A protocol is a set of rules that a class, struct, or enum can adopt. It declares what properties and methods a conforming type must have, but it does not provide the implementation. This lets you write code that works with any type that meets the protocol, regardless of its actual class or structure.
For example, you can define a protocol for a vehicle:
protocol Vehicle {
var numberOfWheels: Int { get }
func startEngine()
}
Any type that conforms to Vehicle must provide a numberOfWheels property and a startEngine() method. You can then write functions that accept any Vehicle:
func describe(_ vehicle: Vehicle) {
print("Wheels: \(vehicle.numberOfWheels)")
vehicle.startEngine()
}
How to Define and Conform to a Protocol
To conform to a protocol, a type declares its adoption in its definition and then implements every requirement. Here’s a struct that conforms to Vehicle:
struct Car: Vehicle {
let numberOfWheels = 4
func startEngine() {
print("Engine started")
}
}
Protocols can also require properties with specific getters and setters. Use { get } for read-only and { get set } for read-write. For methods, you can specify parameters and return types. You can also mark methods as mutating if they change the instance, which is required for structs and enums.
Understanding the Delegate Pattern
The delegate pattern is a way for one object to send messages to another object when an event occurs. The delegating object holds a weak reference to a delegate, which conforms to a protocol. This keeps the two objects loosely coupled—the delegating object doesn’t need to know the delegate’s concrete type.
A classic example is a custom view that notifies a controller when a button is tapped:
protocol CustomViewDelegate: AnyObject {
func customViewDidTapButton(_ view: CustomView)
}
class CustomView {
weak var delegate: CustomViewDelegate?
func buttonTapped() {
delegate?.customViewDidTapButton(self)
}
}
The delegate is declared as weak to avoid retain cycles. The AnyObject constraint limits the delegate to classes, which can be weak. The delegating object calls the delegate method when the event happens, and the delegate decides how to respond.
How to Choose Between Delegate and Other Patterns
Delegation is ideal for one-to-one communication where the delegating object needs to ask the delegate for input or notify it of events. Alternatives include:
- Closures: Use a closure when you need a simple callback without defining a protocol. For example, a button’s tap handler can be a closure property.
- NotificationCenter: Use notifications for one-to-many broadcasts, such as when a user logs out and multiple parts of the app need to react.
- Combine or Swift Concurrency: For reactive streams or async sequences, these frameworks provide more powerful ways to handle events.
Choose delegation when you have a clear relationship between two objects and you want to keep the interface explicit and type-safe. For example, UITableView uses a delegate to handle row selection and a data source to provide content.
Common Mistakes and How to Avoid Them
One frequent mistake is forgetting to mark the delegate as weak, which can create a retain cycle. Always use weak for delegates that are classes. Another error is not conforming to the protocol correctly—missing a required method or property leads to a compile-time error. Read the error message carefully; it lists what’s missing.
Another issue is using a delegate when a closure would be simpler. If you only need a single callback, a closure avoids the boilerplate of defining a protocol. Also, be careful with naming: by convention, delegate methods include the delegating object as the first parameter, as in customViewDidTapButton(_:).
Finally, remember that protocols can inherit from other protocols. This lets you build a hierarchy of requirements, which is useful when you have a base protocol and specialized versions.
Protocol Extensions and Default Implementations
Protocol extensions let you provide default implementations for methods and computed properties. This means conforming types only need to implement the requirements they want to customize. For example:
protocol Greeting {
var name: String { get }
func greet()
}
extension Greeting {
func greet() {
print("Hello, \(name)!")
}
}
struct Person: Greeting {
let name: String
}
let person = Person(name: "Alice")
person.greet() // Prints "Hello, Alice!"
This is powerful for adding behavior to many types without duplicating code. You can also use protocol extensions to add methods that are not part of the protocol requirements, but they are only available when the type is known as the protocol type.
Protocols as Types and Generics
Protocols can be used as types in variables, parameters, and return values. This is called an existential type. For example, you can have an array of Vehicle objects, even if they are different concrete types. However, this can limit performance and access to specific properties. Generics provide a more flexible and performant way to work with protocol-conforming types:
func startAll(_ vehicles: [T]) {
for vehicle in vehicles {
vehicle.startEngine()
}
}
Generics preserve the concrete type, which allows the compiler to optimize and lets you use type-specific features. Use existentials when you need a heterogeneous collection, and generics when the type can be determined at compile time.
Frequently Asked Questions
What is the difference between a protocol and a class?
A class is a concrete type that can have stored properties, inheritance, and deinitializers. A protocol is an abstract set of requirements that any type can adopt. Classes can inherit from one superclass, but they can conform to multiple protocols. Protocols do not store data; they only declare what a conforming type must provide.
When should I use a delegate instead of a closure?
Use a delegate when you have multiple related callbacks or when the communication is two-way and needs to be structured. Closures are better for a single, simple callback. For example, a network request might use a completion closure, while a custom slider might use a delegate to report value changes and interaction events.
Can a struct conform to a protocol with a weak delegate?
Yes, a struct can conform to a protocol, but the delegate property itself must be a class or a class-bound protocol (one that inherits from AnyObject). Since structs are value types, they cannot be referenced weakly. If you need a weak delegate, the delegate must be a class instance.
How do I make a protocol only for classes?
Add AnyObject to the protocol’s inheritance list. For example: protocol MyDelegate: AnyObject { }. This restricts conforming types to classes, which allows you to use weak references to the delegate.
What is a retain cycle and how does it relate to delegates?
A retain cycle occurs when two objects hold strong references to each other, preventing them from being deallocated. In the delegate pattern, if the delegating object holds a strong reference to the delegate and the delegate holds a strong reference back, neither can be released. Declaring the delegate as weak breaks the cycle.
Can I use protocols with SwiftUI?
Yes, SwiftUI uses protocols extensively, such as View and ObservableObject. You can define your own protocols to abstract behavior in your SwiftUI views and models. However, SwiftUI often prefers value types and closures over the delegate pattern, so consider the idiomatic approach for your use case.
Related
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
- Jenkins vs GitHub Actions: Which CI/CD Fits Your Team?
- Turn Off Backup Encryption in iTunes?
- Campus Tech Innovation Hubs: What They Are
- Poco C51 Airtel Prepaid Locked Variant India?
- Shared VPS Hosting: Is It Better?
- Disk Defrag Pro: What It Does and Whether You Need It