iOS 金三银四涨薪系列(2) KVO & KVC

KVO & KVC

KVO用法和底层原理

  • 使用方法:添加观察者,然后怎样实现监听的代理
  • KVO底层使用了 isa-swizling的技术.
  • OC中每个对象/类都有isa指针, isa 表示这个对象是哪个类的对象.
  • 当给对象的某个属性注册了一个 observer,系统会创建一个新的中间类(intermediate class)继承原来的class,把该对象的isa指针指向中间类。
  • 然后中间类会重写setter方法,调用setter之前调用willChangeValueForKey, 调用setter之后调用didChangeValueForKey,以此通知所有观察者值发生更改。
  • 重写了 -class 方法,企图欺骗我们这个类没有变,就是原本那个类。

KVO的优缺点

优点

  • 1、可以方便快捷的实现两个对象的关联同步,例如view & model
  • 2、能够观察到新值和旧值的变化
  • 3、可以方便的观察到嵌套类型的数据变化

缺点

  • 1、观察对象通过string类型设置,如果写错或者变量名改变,编译时可以通过但是运行时会发生crash
  • 2、观察多个值需要在代理方法中多个if判断
  • 3、忘记移除观察者或重复移除观察者会导致crash

怎么手动触发KVO

  • KVO机制是通过willChangeValueForKey:didChangeValueForKey:两个方法触发的。
  • 在观察对象变化前调用willChangeValueForKey:
  • 在观察对象变化后调用didChangeValueForKey:
  • 所以只需要在变更观察值前后手动调用即可。

给KVO添加筛选条件

  • 重写automaticallyNotifiesObserversForKey,需要筛选的key返回NO
  • setter里添加判断后手动触发KVO
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([key isEqualToString:@"age"]) {
        return NO;
    }
    return [super automaticallyNotifiesObserversForKey:key];
}
​
- (void)setAge:(NSInteger)age {
    if (age >= 18) {
        [self willChangeValueForKey:@"age"];
        _age = age;
        [self didChangeValueForKey:@"age"];
    }else {
        _age = age;
    }
}
复制代码

使用KVC修改会触发KVO吗?

  • 会,只要accessInstanceVariablesDirectly返回YES,通过KVC修改成员变量的值会触发KVO。
  • 这说明KVC内部调用了willChangeValueForKey:方法和didChangeValueForKey:方法

直接修改成员变量会触发KVO吗?

不会

KVO的崩溃与防护

崩溃原因:

  • KVO 添加次数和移除次数不匹配,大部分是移除多于注册。
  • 被观察者dealloc时仍然注册着 KVO,导致崩溃。
  • 添加了观察者,但未实现 observeValueForKeyPath:ofObject:change:context:

防护方案1:

  • 直接使用facebook开源框架KVOController

防护方案2:

  • 自定义一个哈希表,记录观察者和观察对象的关系。
  • 使用fishhook替换 addObserver:forKeyPath:options:context:,在添加前先判断是否已经存在相同观察者,不存在才添加,避免重复触发造成bug。
  • 使用fishhook替换removeObserver:forKeyPath:removeObserver:forKeyPath:context,移除之前判断是否存在对应关系,如果存在才释放。
  • 使用fishhook替换dealloc,执行dealloc前判断是否存在未移除的观察者,存在的话先移除。

KVC底层原理

setValue:forKey:的实现

  • 查找setKey:方法和_setKey:方法,只要找到就直接传递参数,调用方法;
  • 如果没有找到setKey:_setKey:方法,查看accessInstanceVariablesDirectly方法的返回值,如果返回NO(不允许直接访问成员变量),调用setValue:forUndefineKey:并抛出异常NSUnknownKeyException
  • 如果accessInstanceVariablesDirectly方法返回YES(可以访问其成员变量),就按照顺序依次查找 _key、_isKey、key、isKey 这四个成员变量,如果查找到了就直接赋值;如果没有查到,调用setValue:forUndefineKey:并抛出异常NSUnknownKeyException

valueForKey:的实现

  • 按照getKey,key,isKey的顺序查找方法,只要找到就直接调用;
  • 如果没有找到,accessInstanceVariablesDirectly返回YES(可以访问其成员变量),按照顺序依次查找_key、_isKey、key、isKey 这四个成员变量,找到就取值;如果没有找到成员变量,调用valueforUndefineKey并抛出异常NSUnknownKeyException
  • accessInstanceVariablesDirectly返回NO(不允许直接访问成员变量),那么会调用valueforUndefineKey:方法,并抛出异常NSUnknownKeyException

Guess you like

Origin juejin.im/post/7075694342265896990