KVO
1. KVO的本质是什么?
KVO的本质实际上是系统在运行时,动态生成了一个类对象NSKVONotifying_Person,让实例对象的isa指向这个新生成的类对象,并且修改了NSKVONotifying_Person中属性set方法的实现。当修改instance对象的属性时,会调用Foundation框架的_NSSetIntValueAndNotify函数。NSKVONotifying_Person是Person的子类,isa指向自己的元类,和Person类对象的元类不同。
Person *p = [[Person alloc] init];
NSLog(@"p.class = %@",object_getClass(p));
// 打印结果为p.class = Person
NSLog(@"selector = %p",[p methodForSelector:@selector(setAge:)]);
/*打印结果为 selector = 0x10497a598
这里通过lldb命令打印方法的IMP
p (IMP)0x10497a598
$1 = 0x000000010497a598 (LinkDemo`-[Person setAge:] at Person.h:14)
*/
[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
p.age = 10;
NSLog(@"p.class = %@",object_getClass(p));
// 打印结果为 p.class = NSKVONotifying_Person
NSLog(@"selector = %p",[p methodForSelector:@selector(setAge:)]);
/* 打印结果为 selector = 0x1807973ac
这里通过lldb命令打印方法的IMP
p (IMP)0x1807973ac
$0 = 0x00000001807973ac (Foundation`_NSSetIntValueAndNotify)
*/
_NSSetIntValueAndNotify函数里面的实现包括:
- willChangeValueForKey
- [super set方法];
- didChangeValueForKey : ==在这个方法中通知observer==
2. 子类NSKVONotifying_Person内部方法?
利用runtime函数打印类对象的方法列表可以得到如下结果:
- 重写了class方法,实际上返回的是Person类对象,但是如果使用runtime函数object_getClass(instance),返回的是NSKVONotifying_Person这个新生成的类对象。
- 重写了set方法,set方法里面调用了C函数_NSSetIntValueAndNotify。
- 重写了dealloc方法。
- 重写了_isKVOA方法。
- (void)printMethodWithClass:(Class) cls {
unsigned int num;
Method *methodList = class_copyMethodList(cls, &num);
NSMutableString *result = [NSMutableString string];
for (int i = 0; i < num; i++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[result appendFormat:@"%@, ",methodName];
}
free(methodList);
NSLog(@"%@ %@",cls,result);
}
/*分别传入添加KVO之前之后的类对象
打印结果为
Person setAge:, age
NSKVONotifying_Person setAge:, class, dealloc, _isKVOA,
*/
3. 如何手动出发KVO?
手动调用willChangeValueForKey和didChangeValueForKey方法。
KVC
1. 什么的KVC?
KVC的全称是Key-Value-Coding键值编码,可以通过一个key来访问对象的某个属性。常用的API有:
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKeyPath:(NSString *)keyPath;
2. 通过KVC修改属性的值会触发KVO吗?赋值取值过程是什么?
通过KVC修改属性的值 会触发KVO。
- 赋值的过程为:
graph TD
setValueForKey: --> Node1{判断是否有key这个属性}
Node1 -->|存在| Node2[ 传递参数,调用方法--willChangeValueForKey,set方法,didChangeValueForKey,这里会触发KVO]
Node1 -->|不存在| Node3{ 调用这个accessInstanceVariablesDirectly类方法,判断是否可以直接访问成员变量,默认返回的YES}
Node3 -->|YES允许访问| Node4{ 按照_key,_isKey,key,isKey顺序查找成员变量}
Node3 -->|NO不允许访问| Node5[ 调用valueForUndefinedKey方法,抛出异常NSUnknownKeyException]
Node4 -->|找到成员变量| Node6[ 调用方法willChangeValueForKey]
Node4 -->|没到成员变量| Node5
Node6 --> Node7{ 是否实现setKey,_setKey方法}
Node7 -->|YES,按照setKey,_setKey这个顺序调用方法,仅调用一个| Node8[ 调用方法didChangeValueForKey,这里会触发KVO]
Node7 -->|NO,给成员变量赋值| Node8
- 取值的过程为:
graph TD
valueForKey --> Node1{ 按照getKey,key,isKey,_age顺序查找方法}
Node1 -->|找到了方法| Node2[ 调用方法]
Node1 -->|没有找到方法| Node3{ 查看accessInstanceVariablesDirectly类方法,默认返回的YES}
Node3 -->|YES允许访问| Node4{ 按照_key,_isKey,key,isKey顺序查找成员变量}
Node3 -->|NO不允许访问| Node5[ 调用valueForUndefinedKey方法,抛出异常NSUnknownKeyException]
Node4 -->|找到成员变量| Node6[ 直接取值]
Node4 -->|没到成员变量| Node5