KVO
基本使用
给 person 对象添加KVO监听
Person *person = [[Person alloc] init];
person.age = 10;
self.person = person;
[self.person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
当监听对象的属性值发生改变时,就会调用。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"属性值更改了 %@", change);
}
移除监听
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"age"];
}
本质分析
Person *person1 = [[Person alloc] init];
person1.age = 1;
self.person1 = person1;
Person *person2 = [[Person alloc] init];
person2.age = 2;
self.person2 = person2;
[self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];

当 person1
使用KVO监听后,person1
的 isa
会指向一个全新的类 NSKVONotifying_Person
,NSKVONotifying_Person
是使用 Runtime
动态创建的一个 Person
类的子类,即 NSKVONotifying_Person
的 superclass
指向的是 Person
类对象。
- 如果对实例对象
person2
的age
进行赋值,person2
会通过 isa 指针找到类Person
,然后找到方法列表中的setAge
进行调用。 - 如果对实例对象
person1
的age
进行赋值,person1
会通过 isa 指针找到类NSKVONotifying_Person
,然后找到方法列表中的setAge
进行调用。但是NSKVONotifying_Person
中的setAge
方法内大致的逻辑是:执行Foundation
框架中的_NSSetIntValueAndNotify
函数,而_NSSetIntValueAndNotify
函数内部会陆续调用willChangeValueForKey
、[super setAge:age]
(原来的setter实现)、didChangeValueForKey
等方法,didChangeValueForKey
方法会执行observeValueForKeyPath: ofObject: change: context:
通知监听器,最终执行代理方法observeValueForKeyPath: ofObject: change: context:
。
因为 age
为 int
类型,所以是 _NSSetIntValueAndNotify
。如果属性 age
是其它类型,相对应的还有 _NSSetBoolValueAndNotify
、_NSSetCharValueAndNotify
、_NSSetObjectValueAndNotify
、_NSSetFloatValueAndNotify
、_NSSetSizeValueAndNotify
、 等。
NSKVONotifying_Person
类中重写了 - (Class)class
方法,使得 [self.person1 class]
为 Person
,而通过运行时 object_getClass(self.person1)
获取时是 NSKVONotifying_Person
。
获取 NSKVONotifying_Person
类中的方法:
Class class = object_getClass(self.person1);
NSMutableArray *methodNames = [NSMutableArray array];
unsigned int count;
Method *methodList = class_copyMethodList(class, &count);
for (NSInteger i = 0; i < count; i ++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames addObject:methodName];
}
free(methodList);
/*
(
"setAge:",
class,
dealloc,
"_isKVOA"
)
*/
NSLog(@"%@", methodNames);
验证
NSLog(@"person1 添加KVO监听之前 类对象(self.person1.isa): %@ 元类对象(self.person1.isa.isa): %@ 方法内存地址: %p", object_getClass(self.person1), object_getClass(object_getClass(self.person1)), [self.person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person2 添加KVO监听之前 类对象(self.person2.isa): %@ 元类对象(self.person2.isa.isa): %@ 方法内存地址: %p", object_getClass(self.person2), object_getClass(object_getClass(self.person2)), [self.person2 methodForSelector:@selector(setAge:)]);
[self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
NSLog(@"person1 添加KVO监听之后 类对象(self.person1.isa): %@ 元类对象(self.person1.isa.isa): %@ 方法内存地址: %p", object_getClass(self.person1), object_getClass(object_getClass(self.person1)), [self.person1 methodForSelector:@selector(setAge:)]);
NSLog(@"person2 添加KVO监听之后 类对象(self.person2.isa): %@ 元类对象(self.person2.isa.isa): %@ 方法内存地址: %p", object_getClass(self.person2), object_getClass(object_getClass(self.person2)), [self.person2 methodForSelector:@selector(setAge:)]);


所以 person1
对象的 setAge:
具体实现是 _NSSetIntValueAndNotify
iOS用什么方式实现对一个对象的KVO?(KVO的本质是什么?)
- 利用 RuntimeAPI 动态生成一个子类,并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的属性时,会调用
Foundation
的_NSSetxxxValueAndNotify
函数
--willChangeValueForKey:
-- 父类原来的setter
--didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法(observeValueForKeyPath:ofObject:change:context:
)
手动调用 willChangeValueForKey:
和 didChangeValueForKey:
可以手动触发KVO
直接修改成员变量不会触发KVO
KVC
KVC的全称是Key-Value Coding,俗称“键值编码”,可以通过一个key来访问某个属性。
常见的API有:
(void)setValue:(id)value forKeyPath:(NSString *)keyPath;
(void)setValue:(id)value forKey:(NSString *)key;
(id)valueForKeyPath:(NSString *)keyPath;
(id)valueForKey:(NSString *)key;
setValue:forKey: 的原理
-
首先按照
setKey:
、_setKey:
的顺序查找方法。如果找到了方法就传递参数,调用方法。 -
如果没找到,则查看
accessInstanceVariablesDirectly
方法 的返回值(默认值是 YES),如果返回 NO,调用setValue: forUndefinedKey:
并抛出异常NSUnknownKeyException
。 -
如果
accessInstanceVariablesDirectly
返回 YES,按照_key
、_isKey
、key
、isKey
的顺序查找成员变量,找到了成员变量直接赋值。 -
如果没找到成员变量,调用
setValue: forUndefinedKey:
并抛出异常NSUnknownKeyException
。
valueForKey: 的原理
-
首先按照
getKey
、key
、isKey
、_key
的顺序查找方法,找到了就调用方法。 -
如果没找到,则查看
accessInstanceVariablesDirectly
方法 的返回值(默认值是 YES),如果返回 NO,调用valueForUndefinedKey:
并抛出异常NSUnknownKeyException
。 -
如果
accessInstanceVariablesDirectly
返回 YES,按照_key
、_isKey
、key
、isKey
的顺序查找成员变量,找到了成员变量直接取值。 -
如果没找到成员变量,调用
valueForUndefinedKey:
并抛出异常NSUnknownKeyException
。
网友评论