iOS 底层 KVC、KVO 相关面试题 - AlvinSunny/OC-TheUnderlying GitHub Wiki
iOS 用什么方式实现对一个对象的KVO ?(KVO的本质是什么?)
利用Runtime API 动态生成一个继承自原有类的子类 NSKVONotifying_XXX, 实现原有类的setter方法 保留成员变量赋值是在原来类中进行,并且让instance对象的isa 指向这个全新的子类;当修改instance对象的属性时,会调用Foundation的_NSSetXXXValueAndNotify函数
1.执行 willChangeValueForKey:
2.父类原来的setter方法
3.执行 didChangeValueForKey: ,内部会触发监听器(Observe)的监听方法--> observeValueForKeyPath:ofObject:change:context
KVO生成的子类重写哪些方法为什么 ?
setAge: 衍生类重写了属性setter方法,用来赋值;但实际赋值操作仍在原有类中实现
class : 衍生类重写class方法是为了不希望开发者了解到系统API的内部实现。直接返回原有类的类对象
dealloc: 衍生类重写dealloc方法便于管理监听的移除
_isKVOA: 此方法是为了给开发者一个可以判定对象是否添加KVO监听服务
如何手动触发KVO ?
KVO一般来说是自动触发的,当属性值发生改变时就通过监听方法收到消息,那手动触发的场景是不是需要在属性值没有被修改的时候,依然需要收到KVO的监听回调 ,比如轮询查询状态值 ?如果是那就需要通过实例对象调用willChangeValueForKey:和didChangeValueForKey:方法 这样就可以实现了
疑问 :如果不调用willChangeValueForKey:只调用didChangeValueForKey:可以吗? 这是不可以的,因为didChangeValueForKey:内部实现是会检查willChangeValueForKey:方法是否被调用了,如果没有被调用是不会触发监听方法的。
KVO有什么注意点 ?
-
移除观察者的注意点,在调用KVO注册方法后,KVO并不会对观察者进行强引用,所以需要注意观察者的生命周期,至少需要在观察者销毁之前移除观察者,否则如果在观察者被释放后再次触发KVO监听方法就会导致Crash
-
KVO的注册方法和移除方法应该是成对的,如果重复调用移除方法就会抛出异常NSRangeExxeption导致程序Crash;官方推荐的方式是在观察者初始化期间(init或viewDidLoad)注册,在dealloc时调用移除
防止多次注册和移除KVO的方式有三种:
-
利用@try @catch(只能针对删除多次KVO的情况),给NSObject增加一个分类,然后利用Runtime的
method_exchangeImplementations交换系统的removerObserver:方法在里面添加@try @catch -
利用模型数组进行存储记录
-
利用observationInfo里私有属性
-
-
如果对象被注册成为观察者,则该对象必须实现监听方法;当被观察者属性发生改变时就会调用监听方法,没有实现就会导致Crash
-
keyPath传入的是一个字符串,为避免写错可以使用NSStringFromSelector(@selector(propertyName)),将属性的getter方法SEL转换成字符串,这样编译阶段就会对keyPath进行检查
-
如果注册方法中context传的是一个对象,必须在移除观察之前持有它的强引用,否则在监听方法中访问ceontext就会Crash
-
如果监听集合对象改变,需要通过KVC的
mutableArrayValueForKey:等方法获取代理对象,并使用代理对象进行操作,当代理对象内部发生变化时会触发KVO,如果直接对集合对象进行操作改变不会触发KVO
通过KVC修改属性会触发KVO么?直接修改成员变量呢 ?
• 通过KVC修改属性会触发KVO, KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性,本质上来说KVC内部在修改成员变量的同时是会主动的调用willChangeValueForKey:和didChangeValueForKey:去触发KVO的,所以才会触发。
• 直接修改成员变量不会触发KVO。直接修改成员变量内部并没有做处理只是单纯的赋值,所以不会触发。
KVC的赋值和取值过程是怎样的?原理是什么?