KVO: key-value observing(键值监听),可用于监听某个对象属性值的改变
添加监听:-addObserver:forKeyPath:options:context
接收监听:-observeValueForKeyPath:ofObject:change:context:
首先我们来测试一下
@interface Person: NSObject {
@property(assign) int age;
}
@end
@implementation ViewController
- (void)viewDidLoad {
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
NSLog(@"person1添加KVO监听之前 - %@(%p) %@(%p)",
object_getClass(self.person1),self.person1,
object_getClass(self.person2),self.person2);
NSLog(@"person1添加KVO监听之前 - %p %p",
[self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]);
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
NSLog(@"person1添加KVO监听之后 - %@(%p) %@(%p)",
object_getClass(self.person1),self.person1,
object_getClass(self.person2),self.person2);
NSLog(@"person1添加KVO监听之后 - %p %p",
[self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]);
NSLog(@"类对象 - %@ %@",
object_getClass(self.person1), // self.person1.isa
object_getClass(self.person2)); // self.person2.isa
}
@end
log:
person1添加KVO监听之前 - Person Person
person1添加KVO监听之前 - 0x1003ce11c 0x1003ce11c
person1添加KVO监听之后 - NSKVONotifying_Person Person
person1添加KVO监听之后 - 0x180b2bd00 0x1003ce11c
类对象 - NSKVONotifying_Person Person
通过LLDB,p (IMP)0x180b2bd00得到:
(IMP) $1 = 0x0000000180b2bd00 (Foundation`_NSSetIntValueAndNotify)
通过log我们可以看到一个很奇怪的现象,添加监听之后person1的class居然变为了一个名为“NSKVONotifying_Person”
然后我们尝试提前自己声明一个类: NSKVONotifying_Person, 这时再执行上面的代码会报错:KVO failed to allocate class pair for name NSKVONotifying_MJPerson, automatic key-value observing will not work for this class
那么要实现一个对象的KVO就明显跟这个NSKVONotifying_Person密切相关了,我们进一步看下这个NSKVONotifying_Person class,我们通过runtime打印这个NSKVONotifying_Person的内部方法看看
@implementation ViewController
- (void)printMethodNamesOfClass:(Class)cls
{
unsigned int count;
// 获得方法数组
Method *methodList = class_copyMethodList(cls, &count);
// 存储方法名
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有的方法
for (int i = 0; i < count; i++) {
// 获得方法
Method method = methodList[i];
// 获得方法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接方法名
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
// 释放
free(methodList);
// 打印方法名
NSLog(@"%@ %@", cls, methodNames);
}
- (void)viewDidLoad {
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
// 给person1对象添加KVO监听
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
[self printMethodNamesOfClass:object_getClass(self.person1)];
[self printMethodNamesOfClass:object_getClass(self.person2)];
}
@end
log:
NSKVONotifying_Person setAge:, class, dealloc, _isKVOA,
Person setAge:, age,
NSKVONotifying_Person重写了Person的setAge:, class, dealloc方法 ;
而重写后的setAge方法调取了Foundation框架中的_NSSetIntValueAndNotify函数;
那么我们总结一下,
在添加监听后,对象发生了什么事?(如何实现KVO? & KVO的本质是什么?)
- 在添加监听后,通过runtime API,创建NSKVONotifying_Person: Person, 且更改person1对象的isa指向NSKVONotifying_Person;
- NSKVONotifying_Person中重写该属性的-setAge:方法为Founddation框架中的函数:_NSSetIntValueAndNotify;
- _NSSetIntValueAndNotify中大概做了:
[self willChangeValueForKey:@"age"];
[super setAge:_age];
[self didChangeValueForKey:@"age"];
而didChangeValueForKey中触发observeValueForKeyPath:ofObject:change:context:
如此便实现了KVO
如何手动触发KVO?
添加监听方法后,手动调用willChangeValueForKey和didChangeValueForKey,即可触发
取消监听后,可以发现person1的isa重新指向了Persion class
网友评论