OC底层学习-KVC
KVC的全称是Key-Value Coding,俗称"键值编码",可以通过一个key来访问某个属性.
- 常见的API:
- (void)setValue:(nullable id)value forKey:(NSString *)key
设置值- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath
设置值(可以根据路径查找赋值)- (nullable id)valueForKey:(NSString *)key
; 取值- (nullable id)valueForKeyPath:(NSString *)keyPath;
取值
1 KVC的本质
1.1 通过KVC赋值会不会触发KVO?
示例代码:
//创建一个person类
@interface Person : NSObject
/** <#des#> */
@property (nonatomic, assign) int age;
@end
@implementation Person
@end
//创建一个监听者类
@interface GYObserver : NSObject
@end
@implementation GYObserver
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"observeValueForKeyPath--- %@",change);
}
@end
//测试代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
GYObserver * observer = [[GYObserver alloc] init];
//给perosn设置KVO监听
[person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
//使用KVC赋值
[person setValue:@20 forKey:@"age"];
//使用完之后,注销KVO
[person removeObserver:observer forKeyPath:@"age"];
}
return 0;
}
打印结果:
这说明使用KVC赋值,是会触发KVO的(因为KVO的本质是替换了set方法的实现),如此说明KVC赋值肯定调用了属性的set方法,下面我们验证下,我们重写age的set方法看看
- (void)setAge:(int)age {
_age = age;
NSLog(@"setAge---- %d", age);
}
结论:结果表明KVC赋值,确实会调用属性的set方法。如果类中没有set方法,也会触发KVO的,没有set方法的结论和分析,可参考考下面的分析过程和面试题1的结论
1.2 setValue:forKey:的原理
- 首先回去找setAge()或则_setAge()方法,两者同时存在的同时执行setAge()方法
示例代码:
```objectivec
//创建一个person类
@interface Person : NSObject
@end
@implementation Person
- (void)setAge:(int)age {
NSLog(@"setAge---- %d", age);
}
- (void)_setAge:(int)age {
NSLog(@"_setAge---- %d", age);
}
@end
//测试代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
//使用KVC赋值
[person setValue:@20 forKey:@"age"];
}
return 0;
}
当我注释_setAge()方法时,保留setAge()方法时,运行结果:setAge---- 20
当我注释setAge()方法是,保留_setAge()方法时,运行结果:_setAge---- 20
由此可见,setValue:forKey:
第一步首先回去寻找属性的setXXX和_setXXX()方法,传递参数调用方法
- 如果上述两个方法都没有找到,那会去查看
+ (BOOL)accessInstanceVariablesDirectly( 是否能够直接访问成员变量->默认返回值YES)
方法的返回值。如果返回NO就不允许直接访问成员变量,然后调用setValue:forUndefinedKey:
方法抛出异常:NSUnknownKeyException(不认识这个key)
,如果返回YES,回去直接按照顺序寻找以下成员变量:_key、_isKey、key、isKey,
**如果找到了成员变量直接赋值,**否则,那还是会调用setValue:forUndefinedKey:
抛出异常, 接下来我们验证下
@interface Person : NSObject
{
@public
int _age;
int _isAge;
int age;
int isAge;
}
@implementation Person
//表示是否能够直接访问成员变量 Directly直接
//默认返回值YES
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
@end
直接运行结果表示第一会找_age这个名称的成员变量赋值
注释_age
之后的运行结果
注释_age
、_isAge
之后的运行结果
注释_age
、_isAge
、age
之后的运行结果
全部注释之后,直接抛出异常
- setValue:forKey:的原理图
2. 面试题
2.1 通过KVC修改属性会触发KVO么?
- 会触KVO (不管有没有set方法,即使KVC直接修改成员变量,也会触发KVO,说明KVC的内部可能做了KVO监听的一些操作)
- 实质上调用KVC赋值,KVC会调用对象的
willChangeValueForKey:
和didChangeValueForKey:
这两个方法,相当于是手动触发KVO。 - 我们在上述的person类中重写这两个方法,然后使用KVO监听,并使用KVC给成员变量赋值
- 打印结果:
2.2 KVC的赋值和取值过程是怎么样的?原理是什么?
-
赋值过程:
- 首先会去找setAge()或则_setAge()方法,两者同时存在的同时执行setAge()方法,如果找到方法直接赋值
- 如果没有找到这两个方法,那会去查看
+ (BOOL)accessInstanceVariablesDirectly( 是否能够直接访问成员变量->默认返回值YES)
方法的返回值。如果返回NO就不允许直接访问成员变量,然后调用setValue:forUndefinedKey:
方法抛出异常:NSUnknownKeyException(不认识这个key)
,如果返回YES,回去直接按照顺序寻找以下成员变量:_key、_isKey、key、isKey,
**如果找到了成员变量直接赋值,**否则,那还是会调用setValue:forUndefinedKey:
抛出异常 - 赋值的原理流程图在在上面
-
取值过程
- 首先会去寻找
getKey、key、isKey、_key
这个四个get方法去,如果找到方法,就直接返回值 - 如果没有找到这几个方法,那会去查看
+ (BOOL)accessInstanceVariablesDirectly( 是否能够直接访问成员变量->默认返回值YES)
方法的返回值。如果返回NO就不允许直接访问成员变量,然后调用setValue:forUndefinedKey:
方法抛出异常:NSUnknownKeyException(不认识这个key)
,如果返回YES,回去直接按照顺序寻找以下成员变量:_key、_isKey、key、isKey,
**如果找到了成员变量直接赋值,**否则,那还是会调用setValue:forUndefinedKey:
抛出异常 - 取值的原理流程图如下:
- 首先会去寻找