如何选择Block、Delegate、Notification首先得分析各自的自责和优劣势。
Block
Block是Apple在C、C++和Objective-C中引入的一种语言特性,允许创建可以捕获周围作用域变量的匿名函数(闭包)。
适用场景:
- 一对一的简单回调:特别是异步操作完成后需要执行的逻辑,例如网络请求回调、文件读写完成回调等。
- 回调逻辑简单且代码进紧凑:当回调代码量少,且与触发点代码上下文紧密相关时,使用Block可以保持代码的连贯性。
- 需要捕获上下文:Block可以方便地捕获当前作用域内的变量,适合需要访问当前环境数据的场景。
- 链式调用:Block常用于链式编程,使代码可读性更高。
优势:
- 代码内聚:回调逻辑写在调用点附近,便于阅读。
- 灵活捕获上下文:自动捕获变量,减少额外代码。
- 轻量:不需要定义额外的协议或方法。
缺点:
- 容易引起循环引用:如果Block内部捕获了self,而self又持有Block,则会导致循环引用。(需使用week解决)
- 嵌套过审可读性差:多层Block嵌套会使代码难以阅读。
- 不适用于复杂回调:多个回调或多种事件类型使用Block会导致代码臃肿。
委托模式
委托模式是一种非常常用的设计模式,主要用于对象之间的通信。它允许一个对象将某些任务或决定交给另一个对象来处理。
适用场景:
- 一对一的复杂交互:当对象需要向外部传递多种事件,例如UITableViewDataSource、UITableViewDelegate。
- 需要高度定制:委托对象可以根据协议提供不同的实现,适合需要灵活扩展的场景。
- 跨多个类型共享行为:多个对象可以实现同一个协议,复用相同的处理逻辑。
- 生命周期事件:视图控制器的生命周期通常使用Delegate(viewDidLoad、viewDidAppear)
优点:
-
职责分离:将事件处理逻辑分离到独立的委托对象中,符合单一职责原则。
-
支持多种事件:通过定义协议方法,可以处理多种不同的回调事件。
-
避免循环引用:通常delegate使用weak引用,不易引起循环引用。
-
可复用性:同一委托对象可以服务于多个同类组件。
缺点:
-
代码分散:回调逻辑分散在委托对象的不同方法中,与触发点分离。
-
模版代码多:需要定义协议、实现多个方法,代码量较大。
-
只能一对一:一个对象通常只能设置一个delegate。
Notification(通知)
Notification模式是一种消息传递机制,用于在不同对象之间进行解耦的通信。主要基于观察者模式实现。
适用场景:
-
一对多的广播:一个事件需要通知多个无关的对象。
-
跨层通信:从深层子控件直接通知到顶层的控制器,避免层层传递。
-
模块间松耦合:通信双方不需要直接引用,通过通知中心进行交互,降低耦合度。
-
系统事件监听:监听键盘弹出/收起、应用进入后台等系统事件。
优势:
-
完全解耦:发送者和接收者不需要知道对方的存在。
-
支持多接受者:一个通知可以同时被多个对象响应。
-
适用于全局事件:适合传递全局状态变化。
缺点:
- 类型不安全:通知使用字符串作为标识,容易拼写错误且编译器无法检查。
- 调试困难:多个地方响应同一个通知时,调试流程较复杂。
- 无法直接返回值:通知是单向广播,不支持接收者返回值给发送者。
- 生命周期管理:注册通知后需要手动移除,否则可能引起崩溃。
综合对比表
| 特性 | Block | 委托模式 | Notification |
|---|---|---|---|
| 通信方向 | 一对一 | 一对一 | 一对多 |
| 耦合度 | 较高(直接引用) | 低(通信协议) | 极低(完全解耦) |
| 代码内聚性 | 高(回调与触发点在一起) | 低(分散在代理方法中) | 低(分散在多个观察者中) |
| 适用场景 | 简单回调、异步结果 | 复杂交互、多事件回调 | 广播、全局事件 |
| 循环引用风险 | 高(需weak打破) | 低(delegate通常为weak) | 低(但需要注意移除观察者) |
| 典型应用 | 请求回调、动画完成回调 | UITableView事件处理 | 全局状态变化(登录/登出) |