面試題引發的思考:
Q: KVC的賦值和取值過程是怎樣的?原理是什么?
KVC的賦值過程:
- 優先調用
setKey:
方法; - 如果
setKey:
方法不存在,則調用_setKey:
方法; - 如果
setKey:
、_setKey:
方法都不存在,則調用accessInstanceVariablesDirectly
方法,返回值為YES
時,按照_key
、_isKey
、key
、isKey
順序查找成員變量。
KVC的取值過程:
- 優先按照
getKey
、key
、isKey
、_key
順序查找方法; - 如果以上方法方法都不存在,則調用
accessInstanceVariablesDirectly
方法,返回值為YES
時,按照_key
、_isKey
、key
、isKey
順序查找成員變量。
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"]);
}
// 打印結果
Demo[1234:567890] age - 10
Demo[1234:567890] name - miaomiao
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
時,按照_age
、_isAge
、age
、isAge
順序查找成員變量。成員變量順序查找
以上結論皆可通過注釋代碼、斷點得到,此文不做詳細介紹。
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
屬性時:
- 優先按照
getAge
、age
、isAge
、_age
順序查找方法;- 如果以上方法方法都不存在,則調用
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);
}
// 打印結果
Demo[1234:567890] willChangeValueForKey - begin
Demo[1234:567890] willChangeValueForKey - end
Demo[1234:567890] didChangeValueForKey - begin
Demo[1234:567890] observeValueForKeyPath - {
kind = 1;
new = 10;
old = 0;
}
Demo[1234:567890] didChangeValueForKey - end
由打印結果可知:
通過KVC修改age
屬性,并沒有調用setAge:
方法,而是直接修改的成員變量_age
;
但是如此也能觸發監聽者的observeValueForKeyPath: ofObject: change: context:
方法。
說明通過KVC修改age
屬性時,會調用willChangeValueForKey:
方法和didChangeValueForKey:
方法。
那么可以證明:
通過KVC修改屬性會觸發KVO。