前言
KVO(Key Value Observing),是观察者模式
在Foundation
中的实现。
原理
KVO原理简单介绍如下:
- 当一个object有观察者时,动态创建这个object的类的子类
- 对于每个被观察的property,重写其
set
方法 - 在重写的
set
方法中调用- willChangeValueForKey:
和- didChangeValueForKey:
通知观察者 - 当一个property没有观察者时,删除重写的方法
- 当没有observer观察任何一个property时,删除动态创建的子类
验证
口说无凭,简单验证下:验证工程GitHub链接
Jack *jack = [[Jack alloc] init];
// breakpoint 1
[jack addObserver:self forKeyPath:kPropertyThatChanges options:NSKeyValueObservingOptionNew context:nil];
// breakpoint 2
jack.name = @"Jack";
[jack removeObserver:self forKeyPath:kPropertyThatChanges];
// breakpoint 3
三处断点打印结果如下:
// breakpoint 1
(lldb) po jack.class
Jack
(lldb) po object_getClass(jack)
Jack
// breakpoint 2
(lldb) po jack.class
Jack
(lldb) po object_getClass(jack)
NSKVONotifying_Jack
// breakpoint 3
(lldb) po jack.class
Jack
(lldb) po object_getClass(jack)
Jack
上面的打印结果说明,在Jack对象被观察时,framework使用runtime
动态创建了一个Jack类的子类NSKVONotifying_Jack
而且为了隐藏这个行为,NSKVONotifying_Jack
重写了- class
方法返回了之前的类,就好像什么也没发生过一样
但是使用object_getClass()
时就暴露了,因为这个方法返回的是这个对象的isa
指针,这个指针指向的一定是这个对象的类对象
为了进一步了解类内部的结构,我们来偷窥一下这个动态类实现的方法,这里借助一个NSObject的扩展NSObject+DLIntrospection
,它封装了打印一个类的方法、属性、协议等常用调试方法,一目了然。
@interface NSObject (DLIntrospection)
+ (NSArray *)classes;
+ (NSArray *)properties;
+ (NSArray *)instanceVariables;
+ (NSArray *)classMethods;
+ (NSArray *)instanceMethods;
+ (NSArray *)protocols;
+ (NSDictionary *)descriptionForProtocol:(Protocol *)proto;
+ (NSString *)parentClassHierarchy;
@end
三个断点打印结果如下:
// breakpoint 1
(lldb) po [object_getClass(jack) instanceMethods]
<__NSArrayI 0x7fd988f0fb40>(
- (void).cxx_destruct,
- (id)name,
- (void)setName:(id)arg0
)
// breakpoint 2
(lldb) po [object_getClass(jack) instanceMethods]
<__NSArrayI 0x7fd988e0ef20>(
- (void)setName:(id)arg0 ,
- (class)class,
- (void)dealloc,
- (BOOL)_isKVOA
)
// breakpoint 3
(lldb) po [object_getClass(jack) instanceMethods]
<__NSArrayI 0x7fd988f0d890>(
- (void).cxx_destruct,
- (id)name,
- (void)setName:(id)arg0
)
从breakpoint2可以看出,动态类重写了4个方法:
- setName
:最主要的重写方法,set值时调用通知函数- class
:为了隐藏自己而重写的方法,返回原来类的class- dealloc
:做清理犯罪现场工作- _isKVOA
:这就是内部使用的标识了,判断这个类有没有被KVO动态生成子类
接下来验证一下KVO重写set方法后是否调用了- willChangeValueForKey:
和- didChangeValueForKey:
最直接的验证方式就是在Jack
类中重写这两个方法:
- (void)willChangeValueForKey:(NSString *)key
{
NSLog(@"%@", NSStringFromSelector(_cmd));
[super willChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key
{
NSLog(@"%@", NSStringFromSelector(_cmd));
[super didChangeValueForKey:key];
}
运行程序发现,被观察的property
发生改变时,的确会调用这两个方法。
参考文章:《objc kvo简单探索》