面试题引发的思考:
Q: KVC的赋值和取值过程是怎样的?原理是什么?
Q: 通过KVC修改属性会触发KVO么?
1. 何为KVC?
KVC的全称是Key-Value Coding,即“键值编码”,可以通过一个key来访问某个属性。
常见API:
- (void)setValue:(id)value forKey:(NSString *)key;
- (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
使用方法如下:
// TODO: ----------------- Cat类 -----------------
@interface Cat : NSObject
@property (nonatomic, copy) NSString *name;
@end
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject
@property (nonatomic, assign) int age;
@property (nonatomic, strong) Cat *cat;
@end
// TODO: ----------------- ViewController类 -----------------
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *person = [[Person alloc] init];
[person setValue:@10 forKey:@"age"];
NSLog(@"age - %@", [person valueForKey:@"age"]);
person.cat = [[Cat alloc] init];
[person setValue:@"miaomiao" forKeyPath:@"cat.name"];
NSLog(@"name - %@", [person valueForKeyPath:@"cat.name"]);
}
打印结果
2. KVC赋值原理是什么?
setValue: forKey: 原理KVC赋值过程如上图,验证方法如下:
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject {
@public
// 按照`_key`、`_isKey`、`key`、`isKey`顺序查找成员变量
int _age;
int _isAge;
int age;
int isAge;
}
@end
@implementation Person
// 优先调用`setAge:`方法
- (void)setAge:(int)age {
NSLog(@"setAge - %d", age);
}
// 如果`setAge:`方法不存在,则调用`_setAge:`方法
- (void)_setAge:(int)age {
NSLog(@"_setAge - %d", age);
}
// 如果`setAge:`、`_setAge:`方法都不存在,则调用`accessInstanceVariablesDirectly`方法,查找成员变量
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
@end
// TODO: ----------------- ViewController类 -----------------
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *person = [[Person alloc] init];
// 通过KVC修改age属性
[person setValue:@10 forKey:@"age"];
}
通过KVC修改
age
属性时:成员变量顺序查找
- 优先调用
setAge:
方法- 如果
setAge:
方法不存在,则调用_setAge:
方法- 如果
setAge:
、_setAge:
方法都不存在,则调用accessInstanceVariablesDirectly
方法,返回值为YES
时,按照_key
、_isKey
、key
、isKey
顺序查找成员变量以上结论皆可通过注释代码、断点得到,此文不做详细介绍。
3. KVC取值原理是什么?
valueForKey: 原理KVC取值过程如上图,验证方法如下:
// TODO: ----------------- ViewController类 -----------------
@interface Person : NSObject {
@public
// 按照`_key`、`_isKey`、`key`、`isKey`顺序查找成员变量
int _age;
int _isAge;
int age;
int isAge;
}
@end
@implementation Person
// 按照`getKey`、`key`、`isKey`、`_key`顺序查找方法
- (int)getAge {
return 11;
}
- (int)age {
return 12;
}
- (int)isAge {
return 13;
}
- (int)_age {
return 14;
}
// 如果以上方法都不存在,则调用`accessInstanceVariablesDirectly`方法,查找成员变量
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
@end
// TODO: ----------------- ViewController类 -----------------
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *person = [[Person alloc] init];
person->_age = 11;
person->_isAge = 12;
person->age = 13;
person->isAge = 14;
NSLog(@"%@", [person valueForKey:@"age"]);
}
通过KVC获取
age
属性时:
- 优先按照
getKey
、key
、isKey
、_key
顺序查找方法- 如果以上方法方法都不存在,则调用
accessInstanceVariablesDirectly
方法,返回值为YES
时,按照_key
、_isKey
、key
、isKey
顺序查找成员变量以上结论皆可通过注释代码、断点得到,此文不做详细介绍。
4. 验证:通过KVC修改属性会触发KVO?
// TODO: ----------------- Person类 -----------------
@interface Person : NSObject {
@public
int _age;
}
@end
@implementation Person
+ (BOOL)accessInstanceVariablesDirectly {
return YES;
}
- (void)willChangeValueForKey:(NSString *)key {
NSLog(@"willChangeValueForKey - begin");
[super willChangeValueForKey:key];
NSLog(@"willChangeValueForKey - end");
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"didChangeValueForKey - begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey - end");
}
@end
// TODO: ----------------- ViewController类 -----------------
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
Person *person = [[Person alloc] init];
// 添加KVO监听
[person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionOld |NSKeyValueObservingOptionNew context:NULL];
// 通过KVC修改age的属性
[person setValue:@10 forKey:@"age"];
// 移除KVO监听
[person removeObserver:self forKeyPath:@"age"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"observeValueForKeyPath - %@", change);
}
KVC修改属性打印结果
由打印结果可知:
通过KVC修改age
属性,并没有调用setAge:
方法,而是直接修改的成员变量_age
;
但是如此也能触发监听者的observeValueForKeyPath: ofObject: change: context:
方法,说明通过KVC修改age
属性时,会调用willChangeValueForKey:
方法和didChangeValueForKey:
方法;
那么可以证明:通过KVC修改属性会触发KVO。
网友评论