Delegates and Protocols Communication Pattern in iOS
Overview
The Delegates and Protocols pattern is a fundamental design pattern in iOS development, particularly within UIKit, for enabling communication between objects. It allows one object (the delegate) to act on behalf of, or in coordination with, another object, facilitating loose coupling and modular code. This document explains the concept, its implementation, and provides practical examples using Swift.
What are Delegates and Protocols?
- Protocol: A blueprint of methods, properties, and other requirements that a conforming type must implement. In Swift, protocols define a contract for communication without specifying the implementation.
- Delegate: An object that conforms to a protocol and handles tasks or events on behalf of another object. The delegate pattern is a one-to-one communication mechanism.
- Purpose: This pattern enables objects to communicate without tightly coupling them, promoting reusability and flexibility.
Key Characteristics
- Loose Coupling: The delegating object doesn't need to know the concrete type of the delegate, only that it conforms to the protocol.
- Event-Driven: Commonly used for handling user interactions, lifecycle events, or data updates.
- Common in UIKit: Used extensively in components like
UITableView
,UITextField
, andUIAlertController
.
How Delegates and Protocols Work
- Define a Protocol: Declare a protocol with methods or properties that the delegate must implement.
- Adopt the Protocol: A class, struct, or enum adopts the protocol by implementing its requirements.
- Set the Delegate: The delegating object holds a reference to the delegate (usually a weak reference to avoid retain cycles).
- Call Delegate Methods: The delegating object invokes protocol methods on the delegate when specific events occur.
Implementing the Pattern
Step 1: Define a Protocol
Protocols are defined using the protocol
keyword. Methods can be required or optional (using @objc
protocols for optional methods).
protocol DataEntryDelegate: AnyObject {
func didEnterData(_ data: String)
func didCancel()
}
AnyObject
ensures only class types can conform, enabling weak references.- Methods like
didEnterData
anddidCancel
define the communication contract.
Step 2: Create a Delegating Object
The delegating object declares a weak
delegate property to avoid retain cycles and calls delegate methods when needed.
class DataEntryViewController: UIViewController {
weak var delegate: DataEntryDelegate?
@objc func saveButtonTapped() {
delegate?.didEnterData("Sample Data")
}
@objc func cancelButtonTapped() {
delegate?.didCancel()
}
}
Step 3: Adopt the Protocol
The delegate (e.g., another view controller) adopts the protocol and implements its methods.
class ParentViewController: UIViewController, DataEntryDelegate {
func didEnterData(_ data: String) {
print("Received data: \(data)")
}
func didCancel() {
print("Data entry cancelled")
}
}
Step 4: Set the Delegate
The parent object sets itself as the delegate of the delegating object.
let dataEntryVC = DataEntryViewController()
dataEntryVC.delegate = self // ParentViewController
Practical Example: Custom Alert with Delegate
Below is a complete example of a custom alert view controller using the delegate pattern to communicate user actions.
import UIKit
// Step 1: Define the protocol
protocol CustomAlertDelegate: AnyObject {
func alertDidConfirm()
func alertDidCancel()
}
// Step 2: Create the delegating view controller
class CustomAlertViewController: UIViewController {
weak var delegate: CustomAlertDelegate?
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
}
private func setupUI() {
view.backgroundColor = .white
let confirmButton = UIButton(type: .system)
confirmButton.setTitle("Confirm", for: .normal)
confirmButton.addTarget(self, action: #selector(confirmTapped), for: .touchUpInside)
confirmButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(confirmButton)
let cancelButton = UIButton(type: .system)
cancelButton.setTitle("Cancel", for: .normal)
cancelButton.addTarget(self, action: #selector(cancelTapped), for: .touchUpInside)
cancelButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(cancelButton)
// Auto Layout
NSLayoutConstraint.activate([
confirmButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
confirmButton.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: -20),
cancelButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
cancelButton.topAnchor.constraint(equalTo: confirmButton.bottomAnchor, constant: 20)
])
}
@objc private func confirmTapped() {
delegate?.alertDidConfirm()
dismiss(animated: true)
}
@objc private func cancelTapped() {
delegate?.alertDidCancel()
dismiss(animated: true)
}
}
// Step 3: Adopt the protocol in a parent view controller
class MainViewController: UIViewController, CustomAlertDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let showAlertButton = UIButton(type: .system)
showAlertButton.setTitle("Show Alert", for: .normal)
showAlertButton.addTarget(self, action: #selector(showAlert), for: .touchUpInside)
showAlertButton.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(showAlertButton)
NSLayoutConstraint.activate([
showAlertButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
showAlertButton.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
@objc private func showAlert() {
let alertVC = CustomAlertViewController()
alertVC.delegate = self
present(alertVC, animated: true)
}
// Step 4: Implement delegate methods
func alertDidConfirm() {
print("User confirmed the alert")
}
func alertDidCancel() {
print("User cancelled the alert")
}
}
How It Works
- Tapping "Show Alert" in
MainViewController
presentsCustomAlertViewController
. - The alert has "Confirm" and "Cancel" buttons that trigger delegate methods.
- The delegate (
MainViewController
) handles the events and prints messages to the console.
UIKit Examples of Delegate Pattern
- UITableViewDelegate: Handles table view events like row selection (
tableView(_:didSelectRowAt:)
). - UITextFieldDelegate: Manages text field interactions, e.g.,
textFieldShouldReturn(_:)
. - UIScrollViewDelegate: Responds to scroll events, e.g.,
scrollViewDidScroll(_:)
.
Example with UITextField
:
class TextInputViewController: UIViewController, UITextFieldDelegate {
let textField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
textField.placeholder = "Enter text"
textField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textField)
NSLayoutConstraint.activate([
textField.centerXAnchor.constraint(equalTo: view.centerXAnchor),
textField.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
print("Text entered: \(textField.text ?? "")")
return true
}
}
Best Practices
- Use Weak References: Always declare the delegate property as
weak
to prevent retain cycles. - Clear Protocol Naming: Name protocols descriptively, e.g.,
CustomAlertDelegate
instead ofDelegate
. - Optional Methods: Use
@objc
protocols withoptional
for non-mandatory methods, but prefer required methods for clarity. - Type Safety: Use
AnyObject
in protocol definitions to ensure delegate compatibility with UIKit’s class-based system. - Documentation: Clearly document the purpose of each delegate method.
Delegates vs. Other Patterns
- Closures: Suitable for simple, one-off callbacks but can lead to retain cycles if not handled carefully.
- Notifications: Better for broadcasting events to multiple observers but less structured than delegates.
- When to Use Delegates: Ideal for one-to-one communication, especially in UIKit, where objects need to respond to specific events.
Resources
Conclusion
The Delegates and Protocols pattern is a cornerstone of iOS development, enabling modular and maintainable communication between objects. By defining clear protocols and using the delegate pattern, you can build flexible UIKit-based apps that handle user interactions and events efficiently. Start with simple delegate implementations and explore UIKit’s built-in delegate protocols to deepen your understanding.