Delegate — 是一种设计模式,其中一个对象将任务的执行委托给另一个对象。在 iOS 中,该模式通过 Swift 协议和 @protocol Objective-C 实现。委托是 Cocoa Touch 的基本模式之一,用于 UITableViewDelegate、UITextFieldDelegate 以及数百个其他 Apple API 中。根据 Apple Developer Documentation (2025),大约 70% 的 UIKit 系统类使用委托来自定义行为而无需继承。
要点
Delegate — 是一种行为设计模式,允许对象将其部分职责转移给另一个对象。与继承(子类重写父类方法)不同,委托使用组合:所有者对象存储对委托的引用,并在特定点调用其方法。
委托定义了一个 协议 — 一组委托可以实现的方法。方法分为必需 (required) 和可选 (optional)。在 Swift 中,协议的可选方法用 @objc optional 关键字标记。
对象 A(所有者)包含 delegate 属性 — 对对象 B(委托)的弱引用。当发生事件时,A 检查 B 是否实现了相应的协议方法并调用它。弱引用 是强制性的:没有它,委托无法从内存中释放,因为所有者通过强引用持有它。
protocol LoaderDelegate: AnyObject {
func loaderDidStart(_ loader: DataLoader)
func loader(_ loader: DataLoader, didLoad data: Data)
func loader(_ loader: DataLoader, didFailWith error: Error)
}
class DataLoader {
weak var delegate: LoaderDelegate?
func start() {
delegate?.loaderDidStart(self)
// 异步加载
}
}DataLoader 类定义了 LoaderDelegate 协议,并在加载生命周期的关键点调用委托的方法。AnyObject 保证协议只能由类实现 — 这对于弱引用是必需的。
委托的实现 在 Swift 中包括三个步骤:声明协议、在所有者中创建弱 delegate 属性、以及在委托类中实现协议。让我们通过一个带有验证的自定义 UITextField 示例来了解。
protocol ValidatorDelegate: AnyObject {
func validate(_ input: String) -> Bool
func validatorDidFail(_ input: String)
}
class ValidatedTextField: UITextField {
weak var validator: ValidatorDelegate?
override func textDidChange() {
guard let text = self.text else { return }
if validator?.validate(text) == false {
validator?.validatorDidFail(text)
self.layer.borderColor = UIColor.red.cgColor
}
}
}
class LoginViewController: UIViewController, ValidatorDelegate {
let textField = ValidatedTextField()
override func viewDidLoad() {
super.viewDidLoad()
textField.validator = self
}
func validate(_ input: String) -> Bool {
return input.count >= 6
}
func validatorDidFail(_ input: String) {
print("验证失败:输入太短")
}
}LoginViewController 实现了 ValidatorDelegate 协议,并将自己设置为 textField 的委托。每次文本更改时,ValidatedTextField 调用 validate(_:),如果验证失败 — 则调用 validatorDidFail(_:)。Controller 充当 View 和验证逻辑之间的中介。
在委托、通知和闭包之间的选择 取决于接收者的数量和组件的耦合程度。每种机制都解决了对象之间的通信问题,但有不同的权衡。
| 特性 | Delegate | NotificationCenter | Closure |
|---|---|---|---|
| 连接类型 | 1:1 | 1:N | 1:1 |
| 耦合度 | 弱(通过协议) | 非常弱(字符串键) | 中等(捕获上下文) |
| 类型安全 | 完整 | 无 (Any?) | 完整 |
| 保留循环风险 | 无 (weak) | 无 | 有 (捕获 self) |
| 何时使用 | 具有多个方法的复杂回调 | 许多人感兴趣的事件 | 具有 1-2 个回调的简单闭包 |
Delegate 在需要将一系列相关事件传递给一个接收者时是最优的。NotificationCenter 更适合广播通知。Closure — 用于简单的异步操作,例如 URLSession 中的 completion handler。
Objective-C 使用 @protocol 和 @optional 来声明委托。与 Swift 不同,协议的所有方法默认都是可选的。关键区别 — 在向委托发送消息之前调用 respondsToSelector:,因为该方法可能尚未实现。
@protocol ImageCacheDelegate
@optional
- (void)cacheDidStartDownload: (ImageCache *)cache;
- (void)cache: (ImageCache *)cache didCacheImage: (UIImage *)image;
@required
- (void)cache: (ImageCache *)cache didFailWithError: (NSError *)error;
@end
@interface ImageCache : NSObject
@property (nonatomic, weak) id<ImageCacheDelegate> delegate;
- (void)downloadImageAtURL: (NSURL *)url;
@end
@implementation ImageCache
- (void)downloadImageAtURL: (NSURL *)url {
if ([self.delegate respondsToSelector:@selector(cacheDidStartDownload:)]) {
[self.delegate cacheDidStartDownload:self];
}
// 异步图像加载
}
@endObjective-C 的关键区别:在调用可选方法之前,需要检查 respondsToSelector:。在 Swift 中,协议的可选方法消除了此检查 — optional chaining (?.) 自动处理缺少实现的情况。
使用委托的错误 会导致内存泄漏、应用程序崩溃和不明显的错误。让我们看看五个最常见的问题。
保留循环 — 最常见的错误。如果 delegate 属性被声明为 strong,而委托又拥有所有者对象,就会形成保留循环。两个对象永远不会从内存中释放。解决方案:在 Swift 中始终将委托声明为 weak var,在 Objective-C 中声明为 @property (weak)。
如果所有者对象比委托存活得更久,并且引用仍然存在,调用委托方法将导致 EXC_BAD_ACCESS。弱引用会自动解决此问题:释放委托后,属性变为 nil。然而,在多线程场景中,建议也在主线程中检查委托。
具有 20+ 个方法的协议违反了接口隔离原则 (ISP)。UITableViewDelegate 包含大约 30 个可选方法 — 这是一个历史例外。在您自己的协议中,最好将责任划分为多个小协议,每个协议有自己的角色。
Apple 系统 API 积极使用 Delegate 模式。让我们看看 UIKit 中三个关键示例,它们出现在每个 iOS 应用程序中。
| API | 协议 | 关键方法 |
|---|---|---|
| UITableView | UITableViewDelegate | didSelectRowAt, heightForRowAt, willDisplay |
| UITextField | UITextFieldDelegate | shouldChangeCharactersIn, didBeginEditing, shouldReturn |
| URLSession | URLSessionDelegate | didReceiveChallenge, didCompleteWithError, didBecomeInvalidWithError |
这些协议中的每一个都实现了行为的不同方面:UITableViewDelegate 管理外观和对触摸的响应,UITextFieldDelegate 控制文本输入,URLSessionDelegate 处理网络事件。这展示了该模式的灵活性:委托可以适应任何责任领域。
常见问题
Delegate 管理行为和外观(单元格高度、触摸响应)。DataSource 提供数据(行数、单元格内容)。UITableViewDelegate 和 UITableViewDataSource — 这是两个独立的协议,分担展示和数据的责任。
弱引用 防止保留循环。所有者(例如 UITableView)只以 weak 方式持有对委托的引用。如果委托(UIViewController)拥有表格,对委托的强引用将创建一个循环:ViewController → UITableView → Delegate (ViewController)。Weak 打破了这个循环。
在 SwiftUI 中,Delegate 模式较少使用 — 它被 @Binding、@State 和闭包取代。然而,delegate 仍然用于通过 UIViewRepresentable 集成 UIKit。例如,MKMapViewDelegate 和 WKUIDelegate 在将 UIKit 组件包装到 SwiftUI 中时仍然相关。
@objc optional 允许在 Swift 协议中声明可选方法。这是与 Objective-C 运行时的兼容性机制。没有 @objc,Swift 协议的所有方法默认都是必需的。Optional 用于 UIKit 协议中,委托可以只实现需要的方法。
一个对象对于每个 delegate 属性只能有 一个委托。如果需要通知多个对象,请使用 multicast delegate、委托数组或 NotificationCenter。Delegate 模式最初被设计为 1:1 关系。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。