美文网首页
底层原理探究(一)KVO

底层原理探究(一)KVO

作者: MaybeLove00 | 来源:发表于2018-05-30 17:33 被阅读0次

    问题:
    1、KVO的使用?实现原理?(为什么要创建子类来实现)
    2、KVC的使用?实现原理?(KVC拿到key以后,是如何赋值的?知不知道集合操作符,能不能访问私有属性,能不能直接访问_ivar)
    3、问setter、getter,比如@property关键字,如果setter、getter是在什么时候调用都不知道,那更别谈kvc了。

    附3篇参考文章
    文章1如何优雅地使用 KVO
    写了一个Demo简单介绍了KVO的使用,并引出 Facebook 开源的 KVOController框架
    注:
    使用FBKVOController有一个坑,自己监听自己的属性时会形成循环引用。如:[self.kvoController observe:self ......] (self->_objectInfosMap->self)。 如果换成 [self.KVOControllerNonRetaining obseve:self ......]不会循环引用,但是又会造成在self这个对象释放->引发kvoControlloer释放时,kvoController取内部的_objectInfosMap取到的观察者为nil, 没法取消kvo观察,导致崩溃。

    文章2Objective-C中的KVC和KVO
    玉令天下的博客,写的蛮不错的。

    文章3iOS底层原理总结 - 探寻KVO本质
    文中多处的实验验证还是挺不错的。

    KVO的两个方法介绍:

    1. 添加监听
    通过以下方法添加一个监听者:
    -(void)addObserver:(NSObject *)observer 
            forKeyPath:(NSString *)keyPath 
               options:(NSKeyValueObservingOptions)options 
               context:(nullable void *)context;
    
    我们重点关注一下这个方法的4个参数:
    observer:
    就是要添加的监听者对象,当监听的属性发生改变时就会去通知该对象,
    该对象必须实现 -observeValueForKeyPath:ofObject:change:context: 方法,
    要不然程序会抛出异常。
    
    keyPath:
    就是要被监听的属性,这里和KVC的规则一样。但是这个值不能传nil,要不然会报错。
    通常我们在用的时候会传一个与属性同名的字符串,但是这样可能会因为拼写错误,导致监听不成功,
    一个推荐的做法是,用这种方式NSStringFromSelector(@selector(propertyName)),
    其实就是是将属性的getter方法转换成了字符串,这样做的好处就是,如果你写错了属性名,
    xcode会用警告提醒你。
    
    options:
    是一些配置选项,用来指明通知发出的时机和通知响应方法
    -observeValueForKeyPath:ofObject:change:context:
    的change字典中包含哪些值,它的取值有4个,定义在NSKeyValueObservingOptions中,
    可以用|符号连接,如下:
    1. NSKeyValueObservingOptionNew:
    指明接受通知方法参数中的change字典中应该包含改变后的新值。
    2. NSKeyValueObservingOptionOld: 
    指明接受通知方法参数中的change字典中应该包含改变前的旧值。
    3. NSKeyValueObservingOptionInitial: 
    当指定了这个选项时,在addObserver:forKeyPath:options:context:消息被发出去后,
    甚至不用等待这个消息返回,监听者对象会马上收到一个通知。
    这种通知只会发送一次,你可以利用这种“一次性“的通知来确定要监听属性的初始值。
    
    4. NSKeyValueObservingOptionPrior:当指定了这个选项时,在被监听的属性被改变前,
    监听者对象就会收到一个通知(一般的通知发出时机都是在属性改变后,
    虽然change字典中包含了新值和旧值,但是通知还是在属性改变后才发出),
    这个通知会包含一个NSKeyValueChangeNotificationIsPriorKeykey,
    其对应的值为一个NSNumber类型的YES。
    
    context:
    添加监听方法的最后一个参数,是一个可选的参数,可以传任何数据,
    这个参数最后会被传到监听者的响应方法中,可以用来区分不同通知,也可以用来传值。
    如果你要用context来区分不同的通知,一个推荐的做法是声明一个静态变量,其保持它自己的地址,
    这个变量没有什么意义,但是却能起到区分的作用,如下:
    static void *PersonAccountBalanceContext = &PersonAccountBalanceContext;
    static void *PersonAccountInterestRateContext = &PersonAccountInterestRateContext;
    
    
    2. 接受通知
    前面说过了,每一个监听者对象都必须实现下面这个方法来接收通知:
    - (void)observeValueForKeyPath:(nullable NSString *)keyPath 
                          ofObject:(nullable id)object 
                            change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change 
                           context:(nullable void *)context;
    
    keyPath,object,context和监听方法中指定的一样,
    关于change参数,它是一个字典,有五个常量作为它的键:
    NSString *const NSKeyValueChangeKindKey; 
    NSString *const NSKeyValueChangeNewKey; 
    NSString *const NSKeyValueChangeOldKey; 
    NSString *const NSKeyValueChangeIndexesKey; 
    NSString *const NSKeyValueChangeNotificationIsPriorKey;
    
    分析下:
    NSKeyValueChangeKindKey:
    指明了变更的类型,值为“NSKeyValueChange”枚举中的某一个,类型为NSNumber。
    enum {
       NSKeyValueChangeSetting = 1,
       NSKeyValueChangeInsertion = 2,
       NSKeyValueChangeRemoval = 3,
       NSKeyValueChangeReplacement = 4
    };
    typedef NSUInteger NSKeyValueChange;
    
    一般情况下返回的都是1也就是第一个NSKeyValueChangeSetting,
    但是如果你监听的属性是一个集合对象的话,当这个集合中的元素被插入,删除,替换时,
    就会分别返回NSKeyValueChangeInsertion,NSKeyValueChangeRemoval和NSKeyValueChangeReplacement。
    
    NSKeyValueChangeNewKey:
    被监听属性改变后新值的key,当监听属性为一个集合对象,
    且NSKeyValueChangeKindKey不为NSKeyValueChangeSetting时,
    该值返回的是一个数组,包含插入,替换后的新值(删除操作不会返回新值)。
    
    NSKeyValueChangeOldKey:
    被监听属性改变前旧值的key,当监听属性为一个集合对象,
    且NSKeyValueChangeKindKey不为NSKeyValueChangeSetting时,
    该值返回的是一个数组,包含删除,替换前的旧值(插入操作不会返回旧值)
    
    NSKeyValueChangeIndexesKey:
    如果NSKeyValueChangeKindKey的值为NSKeyValueChangeInsertion, 
    NSKeyValueChangeRemoval, 或者 NSKeyValueChangeReplacement,
    这个键的值是一个NSIndexSet对象,包含了增加,移除或者替换对象的index。
    
    NSKeyValueChangeNotificationIsPriorKey:
    如果注册监听者是options中指明了NSKeyValueObservingOptionPrior,
    change字典中就会带有这个key,值为NSNumber类型的YES.
    
    最后,完整的change字典大概就类似这样:
    NSDictionary *change = @{ 
                            NSKeyValueChangeKindKey : NSKeyValueChange(枚举值), 
                            NSKeyValueChangeNewKey : newValue, 
                            NSKeyValueChangeOldKey : oldValue, 
                            NSKeyValueChangeIndexesKey : @[NSIndexSet, NSIndexSet], 
                            NSKeyValueChangeNotificationIsPriorKey : @1, 
                            };
    
    
    3、当观察者不需要监听时,可以调用removeObserver:forKeyPath:方法将KVO移除。
    需要注意的是,调用removeObserver需要在观察者消失之前,否则会导致Crash。
    
    注意点
    1、KVO的addObserver和removeObserver需要是成对的,
    如果重复remove则会导致NSRangeException类型的Crash,
    保险的做法是,把remove操作放在try/catch中。
    如果忘记remove则会在观察者释放后再次接收到KVO回调时Crash。
    2、苹果官方推荐的方式是,在init的时候进行addObserver,在dealloc时removeObserver,
    这样可以保证add和remove是成对出现的,是一种比较理想的使用方式。
    

    4. KVO内部实现原理

    1、KVO是通过isa-swizzling(黑魔法)技术实现的
    2、当某个类的属性对象第一次被观察(addObserver)时,系统就会在运行期动态地创建该类的一个派生类,
    在这个派生类中重写基类中任何被观察属性的setter 方法。
    派生类在被重写的setter方法内实现真正的通知机制
    3、如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
    4、每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,
    那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
    
    5、键值观察通知依赖于NSObject 的两个方法: 
    willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, 
    willChangeValueForKey:一定会被调用,这就会记录旧的值。
    而当改变发生后,didChangeValueForKey:会被调用,
    继而 observeValueForKey:ofObject:change:context: 也会被调用。
    6、KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,
    从而达到隐藏生成的派生类
    

    5. _NSsetIntValueAndNotify
    NSKVONotifyin_Person中的setage方法中其实调用了 Fundation框架中C语言函数 _NSsetIntValueAndNotify,_NSsetIntValueAndNotify内部做的操作相当于,首先调用willChangeValueForKey 将要改变方法,之后调用父类的setage方法对成员变量赋值,最后调用didChangeValueForKey已经改变方法。didChangeValueForKey中会调用监听器的监听方法,最终来到监听者的observeValueForKeyPath方法中。

    KVO底层实现.png

    完。

    相关文章

      网友评论

          本文标题:底层原理探究(一)KVO

          本文链接:https://www.haomeiwen.com/subject/yzxzhftx.html