1. KVO的实现原理
- 通过
runtime
派生子类的方式复写相关需要KVO监听的属性, 在该属性setter之前和之后调用NSObject的监听方法, 这样KVO就实现了属性变换前后的回调. - KVO派生的子类具体格式应该是:
NSKVONotifying_+类名
的类, 如NSKVONotifying_Person
- 未使用KVO监听的对象的isa指针结构图
- 使用了KVO监听的对象的isa指针结构图
-
_NSSetValueAndNotify
的内部实现
- 子类的内部方法
-
class
方法屏蔽了内部实现, 隐藏了NSKVONotifying_MJPerson
类的存在, runtime可以拿到真实的类型
2. 如何手动触发一个KVO
- 键值观察通知依赖于NSObject的两个方法
willChangeValueForKey:
didChangeValueForKey:
- 在一个被观察属性发生改变之前,
willChangeValueForKey:
一定会被调用, 这就会记录旧的值. - 当改变发生后,
didChangeValueForKey:
会被调用, 继而observeValueForKey:ofObject:change:context
也会被调用. - 如果可以手动实现这些调用, 就可以实现"手动触发KVO"了.
3. 如何给系统KVO设置筛选条件?
- 如:取消
Person
类age
属性的默认KVO, 设置age大于18时, 手动触发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;
}
}
4. 通过KVC修改属性会触发KVO吗?
- 会触发KVO, 即使没有声明属性, 只有成员变量, 只要
accessInstanceVariablesDirectly
返回的是YES, 允许访问其成员变量, - 那么不管有没有调用setter方法, 通过KVC修改成员变量的值, 都会触发KVO.
- 这也说明通过KVC内部实现了
willChangeValueForKey:
方法和didChangeValueForKey:
方法.
5. 直接修改成员变量会触发KVO吗?
- 不会触发KVO, 直接修改成员变量内部并没有做处理只是单纯的赋值, 所以不会触发KVO.
6. 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、_key:
这四个方法, 只要找到这四个方法当中的任何一个就直接调用该方法; - 如果没有找到, 那么这个时候会查看
accessInstanceVariablesDirectly
方法的返回值, 如果返回的是NO(不允许直接访问成员变量), 那么会调用valueforUndefineKey:
方法, 并抛出异常NSUnknownKeyException
; - 如果
accessInstanceVariablesDirectly
方法返回的是YES, 也就是说可以访问其成员变量, 那么就会按照顺序一次查找_key、_isKey、key、isKey
这四个成员变量, 如果找到了, 就直接取值; 如果依然没有找到成员变量, 那么会调用valueforUndefineKey
方法, 并抛出异常NSUnknownKeyException
.
7. KVC的使用
- 动态地取值和设置值
- 访问和修改私有变量
- 修改一些控件的内部属性
- 如之前的
UITextField
的placeHolderText
.
- 如之前的
- 模型和字典的转换
8. KVO的优缺点
8.1 KVO的优点:
- 能够提供一种简单的方法实现两个对象间的同步.如
Model
和View
之间同步. - 能够对内部对象(非我们创建的对象)的状态改变做出响应, 而不需要改变内部对象的实现.
- 能够提供所观察属性的最新值和之前的旧值.
- 通过
addObserver: forKeyPath
来观察属性, 因此可以观察嵌套对象.
8.2 KVO的缺点
- 我们观察的属性必须使用
String
来定义. 编译时, 编译器不会发出警告及检查. - 对属性的重构会导致我们的观察代码不再可用.
- 所有的观察代码通过一个方法来做, 会生成复杂的if语句.
- 被观察者释放时, 不需要移除观察者, 会造成闪退.
同时我也整理了一些面试题,有需要的朋友可以加QQ群:1012951431 获取
网友评论