我的Github地址 : Jerry4me, 本文章的demo链接 : JRCustomKVODemo
前言
KVO(Key-Value Observing, 键值观察), KVO的实现也依赖于runtime. 当你对一个对象进行观察时, 系统会动态创建一个类继承自原类, 然后重写被观察属性的setter
方法. 然后重写的setter
方法会负责在调用原setter
方法前后通知观察者. KVO还会修改原对象的isa指针指向这个新类.
我们知道, 对象是通过isa指针去查找自己是属于哪个类, 并去所在类的方法列表中查找方法的, 所以这个时候这个对象就自然地变成了新类的实例对象.
不仅如此, Apple还重写了原类的- class
方法, 视图欺骗我们, 这个类没有变, 还是原来的那个类(偷龙转凤). 只要我们懂得Runtime的原理, 这一切都只是掩耳盗铃罢了.
以下实现是参考Glow 技术团队博客的文章进行修改而成, 主要目的是加深对runtime的理解, 大家看完后不妨自己动手实现以下, 学而时习之, 不亦乐乎
KVO的缺陷
Apple给我们提供的KVO不能通过block来回调处理, 只能通过下面这个方法来处理, 如果监听的属性多了, 或者监听了多个对象的属性, 那么这里就痛苦了, 要一直判断判断if else if else….多麻烦啊, 说实话我也不懂为什么Apple不提供多一个传block参数的方法
1 |
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context |
那么, 既然Apple没有帮我们实现, 那我们就手动实现一个呗, 先看下我们最终目标是什么样的 :
1 2 3 4 5 6 |
[object jr_addObserver:observer key:@"name" callback:^(id observer, NSString *key, id oldValue, id newValue) { // do something here }]; [object jr_addObserver:observer key:@"address" callback:^(id observer, NSString *key, id oldValue, id newValue) { // do something here }]; |
简简单单就能让observer监听object的两个属性, 并且监听属性改变后的回调就在对应的callback下, 清晰明了, 何不快哉! Talk is cheep, show you the code!
首先, 我们为NSObject
新增一个分类
NSObject+jr_KVO.h
1 2 3 4 5 6 7 8 9 10 |
#import #import "JRObserverInfo.h" @interface NSObject (jr_KVO) - (void)jr_addObserver:(id)observer key:(NSString *)key callback:(JRKVOCallback)callback; - (void)jr_removeObserver:(id)observer key:(NSString *)key; @end |
添加观察者
在jr_addObserver
方法里我们需要做什么呢?
- 检查对象是否存在该属性的setter方法, 没有的话我们就做什么都白搭了, 既然别人都不允许你修改值了, 那也就不存在监听值改变的事了
- 检查自己(self)是不是一个kvo_class(如果该对象不是第一次被监听属性, 那么它就是kvo_class, 反之则是原class), 如果是, 则跳过这一步; 如果不是, 则要修改self的类(origin_class -> kvo_class)
- 经过第二部, 到了这里已经100%确定self是kvo_class的对象了, 那么我们现在就要重写kvo_class对象的对应属性的setter方法
- 最后, 将观察者对象(observer), 监听的属性(key), 值改变时的回调block(callback), 用一个模型(
JRObserverInfo
)存进来, 然后利用关联对象维护self的一个数组(NSMutableArray *)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
- (void)jr_addObserver:(id)observer key:(NSString *)key callback:(JRKVOCallback)callback { // 1. 检查对象的类有没有相应的 setter 方法。如果没有抛出异常 SEL setterSelector = NSSelectorFromString([self setterForGetter:key]); Method setterMethod = class_getInstanceMethod([self class], setterSelector); if (!setterMethod) { NSLog(@"找不到该方法"); // throw exception here } // 2. 检查对象 isa 指向的类是不是一个 KVO 类。如果不是,新建一个继承原来类的子类,并把 isa 指向这个新建的子类 Class clazz = object_getClass(self); NSString *className = NSStringFromClass(clazz); if (![className hasPrefix:JRKVOClassPrefix]) { clazz = [self jr_KVOClassWithOriginalClassName:className]; object_setClass(self, clazz); } // 到这里为止, object的类已不是原类了, 而是KVO新建的类 // 例如, Person -> JRKVOClassPrefixPerson // JRKVOClassPrefix是一个宏, = @"JRKVO_" 接 : JRCustomKVODemo
前言KVO(Key-Value Observing, 键值观察), KVO的实现也依赖于runtime. 当你对一个对象进行观察时, 系统会动态创建一个类继承自原类, 然后重写被观察属性的 我们知道, 对象是通过isa指针去查找自己是属于哪个类, 并去所在类的方法列表中查找方法的, 所以这个时候这个对象就自然地变成了新类的实例对象. 不仅如此, Apple还重写了原类的 以下实现是参考Glow 技术团队博客的文章进行修改而成, 主要目的是加深对runtime的理解, 大家看完后不妨自己动手实现以下, 学而时习之, 不亦乐乎 KVO的缺陷Apple给我们提供的KVO不能通过block来回调处理, 只能通过下面这个方法来处理, 如果监听的属性多了, 或者监听了多个对象的属性, 那么这里就痛苦了, 要一直判断判断if else if else….多麻烦啊, 说实话我也不懂为什么Apple不提供多一个传block参数的方法
那么, 既然Apple没有帮我们实现, 那我们就手动实现一个呗, 先看下我们最终目标是什么样的 :
简简单单就能让observer监听object的两个属性, 并且监听属性改变后的回调就在对应的callback下, 清晰明了, 何不快哉! Talk is cheep, show you the code! 首先, 我们为 NSObject+jr_KVO.h
添加观察者在
|