KVC跟KVO
# 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;} }
1
# 使用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;
编辑 (opens new window)
上次更新: 2024/10/23, 23:26:17