Delegate is a design pattern where one object delegates task execution to another object through a protocol with predefined methods. In iOS development, Delegate is one of the fundamental Cocoa Touch patterns, used for asynchronous notification without direct coupling between sender and receiver. According to Apple Documentation (2025), delegation is used in Foundation and UIKit for handling table events, network requests, and location management. The pattern ensures loose coupling of components and code reuse.
Key Takeaways
Delegate is an object that implements a specific protocol and receives notifications about events of another object. The Delegation pattern is an alternative to inheritance: instead of creating a subclass to override methods, an object delegates event handling to an external object. In iOS, delegation is implemented through Swift protocols with required and optional methods. The delegate property is always declared as weak var to avoid circular references between objects.
A delegate protocol defines the interaction contract between objects. Required methods must be implemented by the delegate, otherwise the code will not compile. Optional methods are marked with the @objc optional attribute and allow the delegate to respond only to relevant events. Method names follow a convention: the first parameter is the sender object, the second is the event data. For example, tableView(_:didSelectRowAt:) indicates that the sender is UITableView and the data is the selected row index.
// Delegate Protocol
protocol DownloadManagerDelegate: AnyObject {
func downloadManager(_ manager: DownloadManager,
didFinishWith data: Data)
func downloadManager(_ manager: DownloadManager,
didFailWith error: Error)
@objc optional func downloadManager(_ manager: DownloadManager,
didUpdateProgress progress: Float)
}
// Class Using Delegate
class DownloadManager {
weak var delegate: DownloadManagerDelegate?
func startDownload(from url: URL) {
URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
guard let self else { return }
if let error = error {
self.delegate?.downloadManager(self, didFailWith: error)
} else if let data = data {
self.delegate?.downloadManager(self, didFinishWith: data)
}
}.resume()
}
}
The delegate property must be declared as weak var to prevent retain cycles. If the reference were strong, the delegate and the delegating object would hold each other, and ARC would not be able to free their memory. Delegate protocols inherit AnyObject (classes only), which allows using weak. Structures and enums cannot be delegates due to value semantics. An alternative for value types is callback closures.
class ViewController: DownloadManagerDelegate {
let manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self // weak — no retain cycle
manager.startDownload(from: url)
}
func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
processData(data)
}
func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
showError(error)
}
}
The Delegate pattern works on a one-to-one basis: one sender object can have only one delegate at a given time. When an event occurs, the sender checks if the delegate is set and calls the corresponding protocol method. The advantage over direct calls is that the sender does not know the delegate's type, only that it conforms to the protocol. This adheres to the Dependency Inversion Principle (DIP) from SOLID.
The delegate is assigned through assignment: someObject.delegate = self. When the delegate is deallocated, the property automatically becomes nil due to weak semantics. Before calling a delegate method, the delegate is checked via optional chaining: delegate?.method(). If delegate is nil, the call is ignored without a crash. For optional protocol methods, an additional check is used: delegate?.responds(to: #selector(...)), although in Swift this check is usually implicit through optional method declaration.
In a multithreaded environment, delegate is used for asynchronous result return. URLSession provides URLSessionDelegate with methods called when data is received, on timeout, or authentication error. Delegate methods run on URLSession's background queue, so dispatching to the main queue is required for UI updates. Asynchronous delegate does not block the calling thread, allowing other tasks to continue.
class NetworkService: NSObject, URLSessionDataDelegate {
private lazy var session = URLSession(
configuration: .default,
delegate: self,
delegateQueue: OperationQueue()
)
private var receivedData = Data()
func urlSession(_ session: URLSession,
dataTask: URLSessionDataTask,
didReceive data: Data) {
receivedData.append(data)
let progress = Float(receivedData.count) / Float(expectedSize)
DispatchQueue.main.async {
self.progressHandler?(progress)
}
}
func urlSession(_ session: URLSession,
task: URLSessionTask,
didCompleteWithError error: Error?) {
if let error = error {
delegate?.networkService(self, didFailWith: error)
} else {
delegate?.networkService(self, didReceive: receivedData)
}
}
}
Delegate and Callback solve the same problem — asynchronous notification — but in different ways. Delegate uses a protocol with named methods, callback uses a closure with context capture. The choice depends on the number of events, signature complexity, and architectural preferences. Apple recommends delegate for APIs with multiple events (UITableView — 20+ methods) and callback for one-time completions.
Delegate is preferable when handling multiple different events from a single source. For example, CLLocationManager notifies its delegate about location changes, permission errors, geo-fence entry/exit, and service status changes. Each event is a separate protocol method with a clear name and typed parameters. Delegate is also convenient for behavior configuration (should, will, did methods).
Callback is simpler for one-time requests with a single result. Completion handler in URLSession.dataTask takes one line at the call site versus at least three protocol methods. Callback is also more natural for functional chains (map, flatMap, async/await). However, with nesting beyond 2-3 levels, callback becomes Callback Hell, whereas delegate always remains flat.
The iOS SDK contains dozens of built-in delegate protocols for various subsystems. Each is designed for a specific interaction scenario. According to Apple Documentation (2025), the most used delegates are UITableViewDelegate, UITextFieldDelegate, CLLocationManagerDelegate, URLSessionDelegate, and UNUserNotificationCenterDelegate. These protocols contain 3 to 30 methods with varying levels of requirement.
UITableViewDelegate manages the appearance and behavior of table cells. It contains methods for handling row selection, configuring cell height, custom header/footer views, and swipe actions. All protocol methods are optional, allowing only the needed functionality to be implemented. Without a delegate, the table works with default settings. Historically, delegate was combined with UITableViewDataSource.
URLSessionDelegate provides detailed control over HTTP requests. Delegate methods are called when a server response is received, data arrives, or download completes. Specialized sub-protocols URLSessionTaskDelegate and URLSessionDataDelegate extend the base functionality for specific task types. Delegate is required for supporting background downloads, SSL certificates, and custom redirect handling.
| Delegate | Methods | Purpose |
|---|---|---|
| UITableViewDelegate | 25 | Table appearance and interaction |
| UITextFieldDelegate | 8 | Text input and keyboard handling |
| CLLocationManagerDelegate | 12 | Location updates and geofences |
| URLSessionDelegate | 6 | HTTP session and certificate management |
| UNUserNotificationCenterDelegate | 4 | Foreground push notification handling |
Memory management is a critical aspect of working with delegate in iOS. ARC (Automatic Reference Counting) automatically manages memory, but only with proper use of weak/unowned references. Violating the rules leads to memory leaks or premature deallocation. A delegate declared as strong creates a retain cycle if the delegate owner also holds a reference to the delegating object.
A retain cycle occurs when object A (owner) sets itself as delegate of object B, and B holds a strong reference to the delegate. Example: ViewController creates URLSession, sets self as the session's delegate, but URLSession by default holds a strong reference to the delegate if delegateQueue is not specified. The solution is to always check the API documentation for the delegate reference type (weak or strong) and explicitly nil out the delegate in deinit.
class SafeViewController: UIViewController {
private var session: URLSession?
private var service: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
service = NetworkService()
service?.delegate = self
}
deinit {
// Set delegate to nil in deinit — best practice
service?.delegate = nil
session?.invalidateAndCancel()
}
}
// URLSession with weak delegate via NSObject
class WeakDelegateSession: NSObject {
private weak var delegate: URLSessionDelegate?
func createSession() -> URLSession {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return URLSession(
configuration: .default,
delegate: self,
delegateQueue: queue
)
}
}
Before calling a delegate method, you must verify that the delegate exists (not nil) and implements the called method. For required protocol methods, no check is needed — the compiler guarantees implementation. For optional methods, use respond(to:) or optional chaining. If the delegate is deallocated, the weak reference automatically becomes nil, and the delegate call is ignored. This is safe behavior that requires no additional handling.
Developers often make mistakes when working with the Delegate pattern, especially in the early stages of iOS learning. The most common ones include: retain cycle due to strong delegate, forgetting to call delegate?.method(), incorrect protocol method signatures, setting delegate after starting an operation, and multithreading collisions. Let's look at each mistake and how to prevent it.
The most critical mistake is declaring the delegate property as strong var instead of weak var. This creates a retain cycle where neither the delegate nor the delegating object can be freed. Consequences: memory leaks, app slowdown, and hidden bugs. Solution: always use weak var for delegate, and have the protocol inherit from AnyObject to prevent using value types as delegates.
If the delegate is set after calling an asynchronous method, the first events may be lost. Example: calling startDownload() before assigning manager.delegate = self results in missing the completion callback if the download executes synchronously or very quickly. Solution: set the delegate before calling the asynchronous method and document the initialization order in protocol comments.
Frequently Asked Questions
Weak prevents a retain cycle between the delegate and the delegating object. If the reference were strong, objects would hold each other, and ARC would not be able to free them. A weak reference automatically becomes nil when the delegate is deallocated. This is a standard Cocoa Touch practice since the advent of Objective-C and is preserved in Swift for backward compatibility.
Delegate handles events and manages behavior (cell height, response to taps). DataSource provides data for display (number of rows, cells). The delegate answers "how?", dataSource answers "what?". In iOS, both are implemented through protocols, often in the same controller, but are conceptually separate.
No, if the protocol inherits from AnyObject (class protocol). Weak references are only available for reference types (classes). For value types (struct, enum), use callback closures or a separate wrapper class. If you control the protocol, you can avoid inheriting AnyObject, but then weak is prohibited — choose between weak delegate and struct delegate consciously.
responds(to:) is an NSObjectProtocol method that checks whether an object implements the specified selector. It is used to check optional @objc protocol methods before calling them. Without this check, calling an unimplemented optional method would lead to NSInvalidArgumentException. In Swift, for protocols with @objc optional, the check can be implicit through optional binding.
No, delegate is a delegation pattern, not a singleton. Unlike a singleton, a delegate can be replaced at runtime and exists in a single instance for each delegating object. One object can be a delegate for multiple senders. Singleton is a creational pattern that guarantees a single class instance, which has nothing to do with delegation.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also