分类
在一个应用中不同的对象之间需要通过相互传递消息的方式结合起来共同完成任务。iOS 中主要的消息传递机制包括以下几种:
- KVO
- Notification(通知)
- Delegation(代理)
- Block
- Target-Action
既然是消息传递,就一定包含两方面,一方是消息的发送者,另一方是消息的接收者,这个概念可以套用到每一种消息传递机制中,有助于理解。
KVO
KVO 是提供对象属性被改变时的通知的机制。一个支持 KVO 的对象是发送者,监听这个支持 KVO 对象的观察者就是接收者。如果只对某个对象的值的改变感兴趣的话,就可以使用 KVO 消息传递。使用 KVO 需要确保的两点是:1. 接收者需要知道发送者。2. 接收者必须知道发送者的生命周期,因为接收者需要在发送者被销毁前注销观察者身份。另外,这个机制支持一对多。
Notification
发送通知的对象是发送者,监听通知的对象是接收者。通知适用于不相关的模块间传递消息,因为发送者不需要知道接收者。通知是单向的,不能回复一个通知,同时也是支持一对多的。通知体现了设计模式中的观察者模式即订阅模式,不过所有交互都是通过通知中心的,所以就解耦了不同模块之间的联系。
Delegation
在代理机制中,某一个对象是消息的发送者,而实现该对象代理协议的对象就是接收者。代理的作用非常大,通过实现一个对象的代理,可以自定义其一些行为,收到其某些事件的通知。在代理协议定义的方法中,发送者可以通过参数传递不同的内容,接收者可以通过方法的返回值对发送者做出响应。代理机制适用于两个相对较近的模块之间传递消息。要注意过度使用代理,如果两个类结合的很紧密,都不能单独运转,此时其实不需要使用代理,因为两个类都互相知道对方,可以直接交流。
Block
Block 是 Objective-C 后期加入的特性,在很多情况下,Block 都可以起到跟代理一样的作用,当然两者也都有自己的优势。执行 Block 的对象是消息的发送者,而实现该对象的 Block 的对象是消息的接收者。使用 Block 要注意的一个问题是循环引用,在很多需要代理或 Block 的情况中,接收者是知道发送者的,并且引用了发送者,而发送者又维持着 Block 的引用,如果接受者对这个 Block 的实现中又有对接收者 self 的引用,那么此时接收者和发送者之间就出现了循环引用的问题。一种打破循环引用的思路是,如果知道 Block 执行的生命周期,可以在 Block 执行完毕并且之后不需要执行的时候,设置发送者的 Block 为 nil,这样就打破了循环引用。另一种是,如果发送者无法确定 Block 的执行生命周期即无法在某一时刻置空该 Block 属性,那么接收者可以在实现 Block 时使用 weakSelf,这样就不会对接收者维持强引用了。同样的道理,这也是为什么在使用代理时,发送者的 delegate 属性要设置成 weak 引用,否则一样会造成循环引用。使用 Block 的优势是处理的消息和对消息的调用放在一起可以增强可读性。
Target-Action
Target-Action 是回应 UI 事件时典型的消息传递方式。Target-Action 在消息的发送者和接收者之间建立了一个松散的关系。消息的接收者不知道发送者,甚至消息的发送者也不知道消息的接收者会是什么。基于 target-action 传递机制的一个局限是,发送的消息不能携带自定义的信息。
选择
- Target-action 机制非常适合响应 UI 的事件。如果 target 是明确指定的,那么 action 消息会发送给指定的对象。如果 target 是 nil
, action 消息会一直在响应链中被传递下去,直到找到一个能处理它的对象。在这种情况下,我们有一个完全解耦的消息传递机制:发送者不需要知道接收者,反之亦然。 - KVO,主要观察的是发送者的值的改变,当然发送者必须支持 KVO 机制。首先接收者是要知道发送者的,其次就是接收者是要知道发送者的生命周期的,以保证在发送者销毁前注销掉接收者的观察者身份。
- 通知,如果是一对多的需求,而又不适合使用 KVO 的话,那么这种情况下只能使用通知机制了。此外由于通知中心的存在,解耦了消息的发送者和接收者,所以通知也适用于不相关的模块间传递消息,即消息的发送者和接收者可能都不知道对方的存在。
- 其实代理和 Block 的使用场景是基本重合的,在很多情况下两者是可以替换使用的。首先,因为发送者要调用接收者提供的方法执行一些接收者的自定义行为,所以发送者是知道接收者的。而接收者知道发送者却不是必须的,但很多情况下,接收者需要维持发送者的引用,此时就涉及到了要避免循环引用的问题。两种机制都需要处理这个问题,代理的解决办法一般是发送者对于接收者的引用是弱引用,而对于 Block 则是,在无法确定发送者会在某一刻将 Block 的引用置空时,在 Block 内对接收者的 self 引用应使用弱引用,其实本质上是一样的。选择这两种机制考虑到要利用的特性,一个是发送者可以向接收者传递自定义数据,另一个是接收者可以响应发送者向其返回消息。Block 区别于代理的一大特点是,如果希望将接收者调用方法的代码和响应回调的代码写在一处的话,应该选择 Block。但有时如果 Block 内的代码太多,我们还是要把这些代码抽出来放在一个方法里,然后在 Block 内调用,此时看起来就跟代理很像了。不管选用哪一种,在使用时最好统一使用同一种方式,这样可以避免混乱,比如所有 Controller 间的交互方式要统一,所有网络请求的回调方式要统一。
网友评论