Delegate — 是一种设计模式,其中一个对象通过带有预定义方法的协议将任务的执行委托给另一个对象。在iOS开发中,Delegate是Cocoa Touch的基本模式之一,用于在发送方和接收方之间没有直接连接的情况下进行异步通知。根据Apple Documentation(2025),委托应用于Foundation和UIKit中,用于处理表格事件、网络请求和位置管理。该模式确保了组件的松耦合和代码的重用。
要点
Delegate(委托)—— 是实现特定协议并接收另一个对象事件通知的对象。委托模式是继承的替代方案:不是创建子类来重写方法,而是将事件处理委托给外部对象。在iOS中,委托通过具有必需和可选方法的Swift协议实现。delegate属性始终声明为weak var,以避免对象之间的循环引用。
delegate协议定义了对象之间的交互契约。必需方法必须由委托实现,否则代码将无法编译。可选方法使用@objc optional属性标记,允许委托仅对所需事件做出反应。方法名称遵循约定:第一个参数 —— 发送对象,第二个 —— 事件数据。例如,tableView(_:didSelectRowAt:)表示发送者是UITableView,数据是所选行的索引。
// Delegate协议
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)
}
// 使用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()
}
}
delegate属性必须声明为weak var(弱指针)以防止retain cycle。如果引用是强引用,委托和委托对象将相互持有,ARC将无法释放它们的内存。Delegate协议继承AnyObject(仅适用于类),从而允许使用weak。结构和枚举由于值语义而不能成为委托。值类型的替代方案 —— callback闭包。
class ViewController: DownloadManagerDelegate {
let manager = DownloadManager()
override func viewDidLoad() {
super.viewDidLoad()
manager.delegate = self // weak —— 无retain cycle
manager.startDownload(from: url)
}
func downloadManager(_ manager: DownloadManager, didFinishWith data: Data) {
processData(data)
}
func downloadManager(_ manager: DownloadManager, didFailWith error: Error) {
showError(error)
}
}
Delegate模式遵循“一对一”原则:一个发送对象在特定时刻只能有一个委托。当事件发生时,发送方检查delegate是否已设置,并调用协议的相应方法。优点相对于直接调用 —— 发送方不知道委托的类型,只知道它符合协议。这符合SOLID中的依赖倒置原则(DIP)。
委托通过赋值设置:someObject.delegate = self。在委托释放时,由于weak语义,属性自动变为nil。在调用delegate方法之前,通过optional chaining进行检查:delegate?.method()。如果delegate为nil —— 调用被忽略而不会崩溃。对于协议的可选方法,使用额外检查:delegate?.responds(to: #selector(...)),尽管在Swift中此检查通常通过可选方法声明隐式进行。
在多线程环境中,delegate用于异步返回结果。URLSession提供URLSessionDelegate,其方法在接收数据、超时或身份验证错误时被调用。Delegate方法在URLSession的后台队列中执行,因此需要调度到主队列以进行UI更新。异步delegate不会阻塞调用线程,允许继续执行其他任务。
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和Callback解决相同的任务 —— 异步通知 —— 但方式不同。Delegate使用具有命名方法的协议,callback使用带有上下文捕获的闭包。选择取决于事件数量、签名复杂度和架构偏好。Apple建议对于具有多个事件的API使用delegate(UITableView —— 20+方法),对于一次性完成使用callback。
当需要处理来自同一源的多个不同事件时,Delegate更受青睐。例如,CLLocationManager通知委托有关位置变化、权限错误、进入/退出地理区域以及服务状态变化。每个事件都是具有清晰名称和类型化参数的独立协议方法。Delegate也方便配置行为(should、will、did方法)。
对于具有单一结果的一次性请求,Callback更简单。Completion handler在URLSession.dataTask中在调用点只占一行,而协议至少需要三个方法。Callback对于函数链(map、flatMap、async/await)也更自然。然而,当嵌套超过2-3层时,callback会变成Callback Hell,而delegate始终保持扁平。
iOS SDK包含数十个用于不同子系统的内置delegate协议。每个协议都针对特定的交互场景设计。根据Apple Documentation(2025),最常用的delegate是UITableViewDelegate、UITextFieldDelegate、CLLocationManagerDelegate、URLSessionDelegate和UNUserNotificationCenterDelegate。这些协议包含3到30个具有不同强制性的方法。
UITableViewDelegate管理表格单元格的外观和行为。包含用于处理行选择、设置单元格高度、自定义页眉/页脚视图和滑动操作的方法。协议的所有方法都是可选的,允许仅实现所需的功能。如果没有委托,表格将使用默认设置。历史上,delegate与UITableViewDataSource结合使用。
URLSessionDelegate提供对HTTP请求的详细控制。委托方法在接收服务器响应、数据、下载完成时被调用。专门的子协议URLSessionTaskDelegate和URLSessionDataDelegate扩展了特定任务类型的基本功能。Delegate对于支持后台下载、SSL证书和自定义重定向处理是必需的。
| Delegate | 方法数 | 用途 |
|---|---|---|
| UITableViewDelegate | 25 | 表格外观和交互 |
| UITextFieldDelegate | 8 | 文本输入和键盘处理 |
| CLLocationManagerDelegate | 12 | 位置更新和地理区域 |
| URLSessionDelegate | 6 | HTTP会话和证书管理 |
| UNUserNotificationCenterDelegate | 4 | 前台推送通知处理 |
内存管理是在iOS中使用delegate的关键方面。ARC(自动引用计数)自动管理内存,但仅在正确使用weak/unowned引用时。违反规则会导致内存泄漏或过早释放。声明为strong的delegate会产生retain cycle,如果委托的所有者也持有对委托对象的引用。
当对象A(所有者)将自己设置为对象B的委托,而B持有对delegate的强引用时,就会发生retain cycle。示例:ViewController创建URLSession,将self设置为会话的委托,但URLSession默认会保持对delegate的强引用(如果未指定delegateQueue)。解决方案 —— 始终检查API文档中delegate的引用类型(weak或strong),并在deinit中显式将delegate置为nil。
class SafeViewController: UIViewController {
private var session: URLSession?
private var service: NetworkService?
override func viewDidLoad() {
super.viewDidLoad()
service = NetworkService()
service?.delegate = self
}
deinit {
// 在deinit中置空delegate —— 最佳实践
service?.delegate = nil
session?.invalidateAndCancel()
}
}
// 通过NSObject使用weak delegate的URLSession
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
)
}
}
在调用委托的方法之前,需要检查委托是否存在(不为nil)并实现了被调用的方法。对于协议的必需方法,不需要检查 —— 编译器保证实现。对于可选方法,使用respond(to:)或optional chaining。如果委托已被释放,weak引用自动变为nil,委托调用被忽略。这是不需要额外处理的安全行为。
开发人员在使用Delegate模式时经常犯错误,尤其是在学习iOS的初始阶段。最常见:由于strong delegate导致的retain cycle、忘记调用delegate?.method()、协议方法签名错误、在操作开始后设置delegate以及多线程冲突。让我们来看每个错误及其预防方法。
最关键的错误 —— 将delegate属性声明为strong var而不是weak var。这创建了一个retain cycle,其中委托和委托对象都无法被释放。后果:内存泄漏、应用程序变慢和隐藏错误。解决方案:始终对delegate使用weak var,并使协议继承AnyObject,以排除值类型作为委托的使用。
如果在调用异步方法后设置delegate,第一个事件可能会丢失。示例:在分配manager.delegate = self之前调用startDownload(),如果下载是同步或非常快速,将导致完成回调丢失。解决方案:在调用异步方法之前设置delegate,并在协议注释中记录初始化顺序。
常见问题
Weak防止了委托和委托对象之间的retain cycle。如果引用是强引用,对象将相互持有,ARC将无法释放它们。Weak引用在委托释放时自动变为nil。这是自Objective-C出现以来Cocoa Touch的标准做法,并保留在Swift中以保持向后兼容性。
Delegate处理事件并管理行为(单元格高度、对点击的反应)。DataSource提供要显示的数据(行数、单元格)。Delegate回答“如何?”的问题,dataSource回答“什么?”的问题。在iOS中,两者都通过协议实现,通常在同一个控制器中,但在概念上是分开的。
不能,如果协议继承自AnyObject(类协议)。Weak引用仅适用于引用类型(类)。对于值类型(struct、enum),使用callback闭包或单独的包装类。如果您控制协议,可以不继承AnyObject,但那样weak是被禁止的 —— 在weak-delegate和struct-委托之间有意识地选择。
responds(to:) —— NSObjectProtocol的方法,检查对象是否实现了指定的selector。用于在调用前检查协议的@objc可选方法。如果没有此检查,调用未实现的可选方法将导致NSInvalidArgumentException。在Swift中,对于具有@objc optional的协议,检查可以通过optional binding隐式进行。
不是,delegate是委托模式,不是单例。与单例不同,委托可以在运行时被替换,并且对于每个委托对象以一个实例存在。一个对象可以是多个发送方的委托。单例是一种创建型模式,保证类的唯一实例,与委托无关。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。