關(guān)于KVC的探究
基本介紹和使用
KVC全稱(chēng)Key-Value Coding 鍵值編碼,可以通過(guò)Key來(lái)訪(fǎng)問(wèn)某個(gè)屬性诲祸,常見(jiàn)的API:
- (nullable id)valueForKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (nullable id)valueForKey:(NSString *)key;
- (void)setValue:(nullable id)value forKey:(NSString *)key;
創(chuàng)建Person類(lèi)、Animal類(lèi),添加屬性如下:
@interface Animal : NSObject
@property (nonatomic, assign) NSInteger height;
@end
@interface Person : NSObject
@property (nonatomic, assign) NSInteger age;
@property (nonatomic, strong) Animal * dog;
@end
如上拐叉,使用KVC賦值的方式為:
Animal * dog = [[Animal alloc] init];
Person * person = [[Person alloc] init];
person.dog = dog;
// Key
[person setValue:@11 forKey:@"age"];
// KeyPath
[person setValue:@123 forKeyPath:@"dog.height"];
NSLog(@"---- %@ -- %@", @(person.age), @(person.dog.height));
打印結(jié)果:
2018-07-19 23:00:48.546719+0800 KVC[53839:3059207] ---- 11 -- 123
可以看到直接賦值的話(huà)兩中方式都可以,但是類(lèi)似上面的dog.height嵌套的方式必須通過(guò)KeyPath的方式賦值扇商。
KVC原理
進(jìn)入Foundation里面查看 - (void)setValue:(nullable id)value forKey:(NSString *)key;
方法的注解可以了解到凤瘦,KVC的賦值步驟
如下圖:
st=>start: 調(diào)用setValue:forKey
e_findSucceed=>end: 傳遞參數(shù),調(diào)用方法
op1=>operation: Operation1
sub1=>subroutine: My Subroutine
cond_findMethod=>condition: 按照 setKey:
_setKey:
順序查找方法
cond_instanceVariables=>condition: 查看
accessInstance
VariablesDirectly
(默認(rèn)返回YES)
方法的返回值
cond_findIvar=>condition: 按照
_key案铺、_isKey
key蔬芥、isKey
順序查找
成員變量
e_findIvar=>end: 直接賦值
e_exception=>end: 調(diào)用
setValue:forUndefinedKey:
并且拋出
NSUnKnownKeyException
st->cond_findMethod
cond_findMethod(yes, bottom)->e_findSucceed
cond_findMethod(no, right)->cond_instanceVariables
cond_instanceVariables(yes, bottom)->cond_findIvar
cond_findIvar(yes, bottom)->e_findIvar
cond_instanceVariables(no, right)->e_exception
cond_findIvar(no, right)->e_exception
以上為markdown語(yǔ)法,用的 MWebLite編寫(xiě)的控汉,奈何blog不支持笔诵,所以直接將結(jié)果導(dǎo)圖截圖貼在下面:
進(jìn)入Foundation里面查看 - (nullable id)valueForKey:(NSString *)key;
方法的注解可以了解到,KVC的取值步驟
如下圖:
st=>start: 調(diào)用valueForKey:
e_findSucceed=>end: 調(diào)用方法
op1=>operation: Operation1
sub1=>subroutine: My Subroutine
cond_findMethod=>condition: 按照 getKey
key姑子、isKey乎婿、
_key、_getKey
順序查找方法
cond_instanceVariables=>condition: 查看
accessInstance
VariablesDirectly
(默認(rèn)返回YES)
方法的返回值
cond_findIvar=>condition: 按照
_key街佑、_isKey
key谢翎、isKey
順序查找
成員變量
e_findIvar=>end: 直接取值
e_exception=>end: 調(diào)用
valueForUndefinedKey:
并且拋出
NSUnKnownKeyException
st->cond_findMethod
cond_findMethod(yes, bottom)->e_findSucceed
cond_findMethod(no, right)->cond_instanceVariables
cond_instanceVariables(yes, bottom)->cond_findIvar
cond_findIvar(yes, bottom)->e_findIvar
cond_instanceVariables(no, right)->e_exception
cond_findIvar(no, right)->e_exception
接下來(lái)我們來(lái)驗(yàn)證一下:
setValue:forKey:賦值
使用上面的Person類(lèi),將屬性全部刪除沐旨,添加以下成員變量森逮,重寫(xiě)兩個(gè)set方法:
@interface Person : NSObject {
@public
int _age;
int _isAge;
int age;
int isAge;
}
@end
- (void)setAge:(NSInteger)age {
NSLog(@"--- setAge");
}
- (void)_setAge:(NSInteger)age {
NSLog(@"--- _setAge");
}
然后調(diào)用KVC給age賦值,可以看打印結(jié)果:
2018-07-20 14:41:18.679275+0800 KVC[65810:3455316] --- setAge
此時(shí)調(diào)用的是 setAge:
方法磁携,注釋掉 setAge:
方法再次運(yùn)行:
2018-07-20 14:41:43.561291+0800 KVC[65834:3456053] --- _setAge
當(dāng)這兩個(gè)方法都沒(méi)有實(shí)現(xiàn)的時(shí)候就會(huì)調(diào)用 accessInstanceVariablesDirectly
方法褒侧,若返回YES,則直接給成員變量賦值,如沒(méi)有或返回值為NO則會(huì)調(diào)用 setValue:forUndefinedKey:
并且拋出NSUnKnownKeyException異常璃搜。
valueForKey:取值
重寫(xiě)流程圖中的get方法:
- (int)getAge {
return 11;
}
- (int)age {
return 12;
}
- (int)isAge {
return 13;
}
- (int)_age {
return 14;
}
- (int)_getAge {
return 15;
}
打印結(jié)果:
NSLog(@"---- %@", [person valueForKey:@"age"]);
如上我們可以查看當(dāng)調(diào)用過(guò)的方法后就注釋?zhuān)钡綀?zhí)行完成所有的方法拖吼。我們可以控制 accessInstanceVariablesDirectly
方法的返回值來(lái)證明我們想要的結(jié)果。
思考一下KVC能觸發(fā)KVO嗎这吻?
我們來(lái)測(cè)試一下吊档,添加Observer類(lèi)來(lái)監(jiān)聽(tīng)并輸出Log:
@implementation Observer
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey, id> *)change context:(void *)context {
NSLog(@"observeValueForKeyPath - %@", change);
}
@end
測(cè)試代碼:
Observer * ob = [Observer new];
Person * person = [[Person alloc] init];
[person addObserver:ob forKeyPath:@"age" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:nil];
[person setValue:@1 forKey:@"age"];
[person removeObserver:ob forKeyPath:@"age"];
打印結(jié)果為:
2018-07-20 15:19:30.526730+0800 KVC[67145:3507833] observeValueForKeyPath - {
kind = 1;
new = 1;
old = 0;
}
可以看到KVC確實(shí)調(diào)用了KVO,上一篇文章中我們了解到了KVO的實(shí)現(xiàn)唾糯,接下來(lái)可以大概驗(yàn)證一下:
在Person類(lèi)中實(shí)現(xiàn)如下方法:
- (void)willChangeValueForKey:(NSString *)key {
NSLog(@"willChangeValueForKey");
[super willChangeValueForKey:key];
}
- (void)didChangeValueForKey:(NSString *)key {
NSLog(@"didChangeValueForKey-- begin");
[super didChangeValueForKey:key];
NSLog(@"didChangeValueForKey-- end");
}
打印結(jié)果:
2018-07-20 15:23:34.761496+0800 KVC[67256:3513685] willChangeValueForKey
2018-07-20 15:23:34.761836+0800 KVC[67256:3513685] didChangeValueForKey-- begin
2018-07-20 15:23:34.762151+0800 KVC[67256:3513685] observeValueForKeyPath - {
kind = 1;
new = 1;
old = 0;
}
2018-07-20 15:23:34.762202+0800 KVC[67256:3513685] didChangeValueForKey-- end
可以看到打印結(jié)果跟KVO是一樣的怠硼,這里可以猜測(cè)蘋(píng)果大大在KVC內(nèi)部的實(shí)現(xiàn):
[person willChangeValueForKey:@"age"];
person->_age = 1;
[person didChangeValueForKey:@"age"];
KVC相當(dāng)于手動(dòng)調(diào)用了KVO。