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、可以方便快捷的实现两个对象的关联同步,例如
- 缺点
- 1、观察对象通过
string
类型设置,如果写错或者变量名改变,编译时可以通过但是运行时会发生crash
- 2、观察多个值需要在代理方法中多个
if
判断 - 3、忘记移除观察者或重复移除观察者会导致
crash
- 1、观察对象通过
怎么手动触发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
;
多线程
进程和线程的区别
- 进程:进程是指在系统中正在运行的一个应用程序,一个进