前言
Delegate 和 Notification 和 KVO 是几种常见的传值模式。
在我们实际的开发中经常会用到。
在一些iOS的面试中也是经常被提及,也是衡量一个iOS水平的指标。
那么,这种模式的应用场景是什么?iOS-SDK内部是如何实现的? 它们之间的区别又是什么?
这篇文章中,我会为大家娓娓道来。
Delegate (代理,委托,协议)
我总结的Delegate的特点如下:
- 1对1的传值
- 支持正向与反向传值 (参数,返回值)
- 可以用require和optional来修饰的
在delegate中,我们经常会看到 should 字眼, 我们以UITextFieldDelegate举例:
通常这样的情况,都会有个返回值。
这就是所谓接受者的态度。你可以在Controller里通过实现UITextFieldDelegate来控制TextFild的Return键是否有响应。
在UITextFaild内部一定存在这样的代码:
- (void)xxxxMethod
{
//看看delegate实现时的返回值
BOOL isOK = [_delegate textFieldShouldReturn:self];
if (isOK) {
} else {
}
}
复制代码
同时,在这个例子中,我们也看到了,正向与反向的传值。
Controller通过实现UITextFieldDelegate方法,得到了参数UITextFaild,同时处理了一个BOO类型的返回值给UITextField。
同样的例子还有很多, 比如我们常见的UITableView。
有些朋友看到DataSource懵了, 其实不管叫什么都是Delegate.
再来看两个核心的delegate方法:
- 你的controller告诉tableview有多少行
- 你的controller告诉tableview绘制的Cell是什么样的
这些都是通过返回值实现的。
注意:
所以在用delegate时,我们在用delegate时,除了正常的通过参数传值,还要灵活的去运用返回值。
Notification (通知,消息)
我总结Notification有如下特点:
- 1对N (多)
- 不关心返回值,单向的传值
- NSNotificationCenter单例统一处理发通知
- 通过不同的唯一的通知标识名NotificationName区分不同通知
- 被观察者主动发出通知
使用Notification一定要谨慎,由于1对多的缘故,避免滥用,不好查问题。
其次就是在Controller销毁时,一定要注销掉通知。
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
复制代码
KVO
- 1对N (多)
- 只能监听对象的属性变化 (比较局限)
- 只有通过getters和setters来改变值才会触发KVO
- 被观察者不用添加任何代码(比如发送通知),与被观察者完全解耦
- 注册观察者时,属性名都是通过NSString来查找,容易出错
- KVO的要求是对象必须能支持kvc机制 (NSObject的子类)
- 单向传值
KVO原理
验证KVO的原理很复杂,网上文章很多,也可以自己写demo去验证,我这里就简单的说明:
首先,你要注册一个观察者,观察一下tableview的偏移量:
//添加监听者
[self.tableView addObserver: self forKeyPath: @"contentOffset" options: NSKeyValueObservingOptionNew context: nil];
复制代码
此时,编译器会修改监听对象(tableView)的isa指针,让这个指针指向一个新生成的(官方文档提及的)中间类。
其实这个中间类,就是带有NSKVONotifying_前缀的tableView的子类。
然后,它要通过setter,getter等方法,检测一下你注册的这个属性(contentOffset)是否存在有效?
不存在就直接抛异常崩溃了!
在重新实现setter方法的时候,有两个重要的方法:willChangeValueForKey和didChangeValueForKey,分别在赋值前后进行调用。
此外,还要遍历所有的回调监听者,然后通知这些监听者。
这时作为观察者,我们可以得到对象属性的变化值
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context
{
}
复制代码
所有的监听者通过动态绑定的方式将其存储起来,但这样也会产生强引用,所以要释放
- (void)dealloc
{
[self.tableView removeObserver: self forKeyPath: @"contentOffset"];
}
复制代码
这里只是抛砖引玉,其实iOS-sdk中的实现远不止这些,还要复杂的多,多了解底层的原理,学习好的思路,有助于我们写出更优秀的代码。
FAQ
-
为什么带NSKVONotifying_前缀?
避免重复生成(你可能监听N个属性)
-
如何动态生成类?
看Runtime!
运用场景
我举几个简单的例子,供参考:
用过QQ,微信的对这张图不陌生
这里会实时更新最新的一条聊天记录,包括内容,时间,未读数等等。
我们假设这是一个MessageItem对象。
在Cell内部,监听一下个MessageItem的content,time,unread等几个属性,发生变化时给cell的label控制赋值即可。
其它场景还有很多,比如新闻类的APP,在列表中,读过的都是浅灰色的背景,可以监听下unread属性。
写了KVO又不得不提KVC,后面我会专门写一篇文章,专门讲解KVO与KVC的知识。
还有一点,Runtime有空一定要多学习!