本文參考:
KVC 簡介
KVC全稱是Key Value Coding(鍵值編碼)垦梆,是一個基于NSKeyValueCoding非正式協(xié)議實現(xiàn)的機制驮吱,它可以直接通過key值對對象的屬性進行存取操作,而不需通過調(diào)用明確的存取方法铁瞒。這樣就可以在運行時動態(tài)在訪問和修改對象的屬性势似,而不是在編譯時確定歌豺。
KVC提供了一種間接訪問屬性方法或成員變量的機制推穷,可以通過字符串來訪問對象的的屬性方法或成員變量。
在實現(xiàn)了訪問器方法的類中类咧,使用點語法和KVC訪問對象其實差別不大馒铃,二者可以任意混用(因為KVC會首先搜索訪問器方法,見下文)痕惋。但是沒有訪問器方法的類中区宇,點語法無法使用,這時KVC就有優(yōu)勢了血巍。
KVC和KVO都是基于OC的動態(tài)特性和Runtime機制的萧锉。
KVC 通用的訪問方法
- 通用的訪問方法:
- getter方法:
valueForKey:
- setter方法:
setValue:forKey:
- 衍生的keyPath方法,用來進行深層訪問(key使用點語法)述寡,也可單層訪問:
- keyPath的setter方法:
setValue: forKeyPath:
- keyPath的getter方法:
valueForKeyPath:
示例:
Address.h:
#import <Foundation/Foundation.h>
@interface Address : NSObject
@property (copy, nonatomic) NSString *city;
@property (copy, nonatomic) NSString *street;
@end
Person.h:
#import <Foundation/Foundation.h>
#import "Address.h"
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@property (assign, nonatomic) NSInteger *sex;
@property (strong, nonatomic) NSNumber *age;
@property (strong, nonatomic) Address *address;
@end
- (void)viewDidLoad {
[super viewDidLoad];
Person *myself = [[Person alloc] init];
[myself setValue:@"xds" forKey:@"name"];
NSLog(@"-------name = %@",myself.name);
NSLog(@"-------name = %@",[myself valueForKey:@"name"]);
/**
keyPath的setter方法:setValue: forKeyPath:
keyPath的getter方法:valueForKeyPath:
keyPath為多級訪問柿隙,使用點語法
*/
//注意叶洞,這里要想使用keypath對adress的屬性進行賦值,必須先給myself賦一個Address對象
Address *myAddress = [[Address alloc] init];
[myself setValue:myAddress forKey:@"address"];
//KeyPath為多級訪問
[myself setValue:@"rizhao" forKeyPath:@"address.city"];
NSLog(@"-------city = %@",myself.address.city);
NSLog(@"-------city = %@",[myself valueForKeyPath:@"address.city"]);
}
keypath
除了對當(dāng)前對象的屬性進行賦值外禀崖,還可以對其更“深層”的對象進行訪問衩辟。
keypath可以訪問到array數(shù)組中所有存儲的對象的屬性,前提是對象類型是一樣的波附。
例如下面例子中艺晴,通過valueForKeyPath:將數(shù)組中所有對象的name屬性值取出,并放入一個數(shù)組中返回掸屡。
NSArray *names = [array valueForKeyPath:@"name"];
KVC 的多值操作
批量取值操作
KVC還有更強大的功能封寞,可以根據(jù)給定的一組key,獲取到一組value仅财,并且以字典的形式返回狈究。
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
批量賦值操作
同樣,也可以通過KVC進行批量賦值盏求。使用對象調(diào)用setValuesForKeysWithDictionary:方法時抖锥,可以傳入一個包含key、value的字典進去碎罚,KVC可以將所有數(shù)據(jù)按照屬性名和字典的key進行匹配磅废,并將value給User對象的屬性賦值。
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;
示例
示例如下:
//批量賦值
NSDictionary *dic = @{
@"name":@"xiaoMing",
@"sex":@1,
@"age":@12,
@"address":myAddress
};
[myself setValuesForKeysWithDictionary:dic];
//批量取值
NSArray *keys = @[@"name",@"age",@"sex",@"address"];
NSDictionary *values = [myself dictionaryWithValuesForKeys:keys];
NSLog(@"%@",values);
輸出:
2018-08-25 15:45:57.316650+0800 KVC[1145:231458] {
address = "<Address: 0x60400003cc40>";
age = "<null>";
name = xds;
sex = 0;
}
使用 KVC 進行字典轉(zhuǎn)模型
可以使用 setValuesForKeysWithDictionary: 進行字典轉(zhuǎn)模型荆烈。
假如傳過來的Jason數(shù)據(jù)如下所示拯勉,注意到age傳過來的的key為Age,這樣在字典轉(zhuǎn)模型時會報錯憔购,因為這個Age在模型類里面并沒有定義谜喊。
JSON數(shù)據(jù):
{
@"name":@"xiaoMing",
@"sex":@1,
@"Age":@12
}
我們可以在Person里重寫 setValue:(id)value forUndefinedKey: 方法,這個方法是針對碰到未定義的key時怎么辦的方法倦始。
#import "Person.h"
@implementation Person
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
if ([key isEqualToString:@"Age"]) {
[self setValue:value forKey:@"age"];
}
}
@end
注意點
- Json里的字段數(shù)量和字段名字應(yīng)該和model類所匹配,Json少了字段不會出現(xiàn)問題山卦,但是多了或者是字段名不對鞋邑,就會崩潰。
- 不需對基本數(shù)據(jù)類型做處理账蓉,例如int型轉(zhuǎn)NSNumber枚碗,內(nèi)部會自動作出處理
- NSArray和NSDictionary等集合對象,value都不能是nil铸本,否則會導(dǎo)致Crash
異常信息和異常處理
當(dāng)根據(jù)KVC搜索規(guī)則肮雨,沒有搜索到對應(yīng)的key或者keyPath,則會調(diào)用對應(yīng)的異常方法箱玷。異常方法的默認(rèn)實現(xiàn)怨规,在異常發(fā)生時會拋出一個NSUndefinedKeyException的異常陌宿,并且應(yīng)用程序Crash。
我們可以重寫下面兩個方法波丰,根據(jù)業(yè)務(wù)需求合理的處理KVC導(dǎo)致的異常:
- 在取值時壳坪,未有對應(yīng)的key:
- (nullable id)valueForUndefinedKey:(NSString *)key;
- 在賦值時,未有對應(yīng)的key:
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
當(dāng)通過 KVC 給某個非對象的屬性賦值為 nil 時掰烟,例如使用 setValue:forkey 給 int 或 float 類型屬性賦為 nil 時爽蝴,會拋出 NSInvalidArgumentException 的異常并崩潰。
我們可以重寫下面的方法纫骑,進行處理:
- (void)setNilValueForKey:(NSString *)key;
示例:
當(dāng)我們?yōu)镻erson類的NSInteger類型的sex屬性賦nil時蝎亚,會報錯
[myself setValue:nil forKey:@"sex"];
Person.m 重寫 setNilValueForKey: 方法
- (void)setNilValueForKey:(NSString *)key{
if ([key isEqualToString:@"sex"]) {
[self setValue:@1111 forKey:@"sex"];
}else{
[super setNilValueForKey:key];
}
}
集合屬性操作
當(dāng)我們要操作一個對象里的集合屬性時(NSArray、NSSet等)先馆,我們可以通過KVC方法取到集合屬性发框,然后通過集合屬性操作集合中的元素。實際開發(fā)中最常用的方法磨隘,叫做間接操作缤底。
直接操作要實現(xiàn)下面的方法,但通常不會用到:
有序集合對應(yīng)方法如下:
-countOf<Key>//必須實現(xiàn)番捂,對應(yīng)于NSArray的基本方法count:2 -objectIn<Key>AtIndex:
-<key>AtIndexes://這兩個必須實現(xiàn)一個个唧,對應(yīng)于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:
-get<Key>:range://不是必須實現(xiàn)的,但實現(xiàn)后可以提高性能设预,其對應(yīng)于 NSArray 方法 getObjects:range:
-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes://兩個必須實現(xiàn)一個徙歼,類似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:
-removeObjectFrom<Key>AtIndex:
-remove<Key>AtIndexes://兩個必須實現(xiàn)一個,類似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:
-replaceObjectIn<Key>AtIndex:withObject:
-replace<Key>AtIndexes:with<Key>://可選的鳖枕,如果在此類操作上有性能問題魄梯,就需要考慮實現(xiàn)之
無序集合對應(yīng)方法如下:
-countOf<Key>//必須實現(xiàn),對應(yīng)于NSArray的基本方法count:
-objectIn<Key>AtIndex:
-<key>AtIndexes://這兩個必須實現(xiàn)一個宾符,對應(yīng)于 NSArray 的方法 objectAtIndex: 和 objectsAtIndexes:
-get<Key>:range://不是必須實現(xiàn)的酿秸,但實現(xiàn)后可以提高性能,其對應(yīng)于 NSArray 方法 getObjects:range:
-insertObject:in<Key>AtIndex:
-insert<Key>:atIndexes://兩個必須實現(xiàn)一個魏烫,類似于 NSMutableArray 的方法 insertObject:atIndex: 和 insertObjects:atIndexes:
-removeObjectFrom<Key>AtIndex:
-remove<Key>AtIndexes://兩個必須實現(xiàn)一個辣苏,類似于 NSMutableArray 的方法 removeObjectAtIndex: 和 removeObjectsAtIndexes:
-replaceObjectIn<Key>AtIndex:withObject:
-replace<Key>AtIndexes:with<Key>://這兩個都是可選的,如果在此類操作上有性能問題哄褒,就需要考慮實現(xiàn)之
使用 KVC 進行集合類的運算
KVC 提供的 valueForKeyPath: 方法非常強大稀蟋,可以在keyPath中嵌套集合運算符對集合中的對象進行相關(guān)的運算,例如求一個數(shù)組中所有Person對象的age總和呐赡。集合對象主要指NSArray和NSSet退客,不包括NSDictionary。
集合運算符的格式
keyPathToCollection.@collentionOperator.keyPathToproperty
- keyPathToCollection:Left key path,要操作的集合對象萌狂,若調(diào)用 valueForKeyPath: 方法的對象本來就是集合對象档玻,則可以省略;
- collentionOperator:Collection operator粥脚,集合操作符窃肠,一般以@開頭;
- keyPathToproperty:Right key path刷允,要運算的屬性冤留。
//Address.h
@interface Address : NSObject
@property (copy, nonatomic) NSString *city;
@property (copy, nonatomic) NSString *street;
@property (strong, nonatomic) NSNumber *cityNumber;
@property (assign, nonatomic) NSInteger streetNumber;
@end
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *array = [NSMutableArray array];
for ( int i = 0 ; i < 5 ; i++ ) {
Address *address = [[Address alloc] init];
NSString *cityStr = [NSString stringWithFormat:@"city-%d",i];
//批量賦值
NSDictionary *dic = @{
@"city":cityStr,
@"street":@"street",
@"cityNumber":@(i),
@"streetNumber":@101
};
[address setValuesForKeysWithDictionary:dic];
[array addObject:address];
}
//返回數(shù)組中保存的對象的屬性,返回值為數(shù)組
NSArray *cityArray = [array valueForKeyPath:@"city"];
NSLog(@"%@",cityArray);
}
集合運算符的分類
集合運算符主要分為三類:
- 集合操作符:處理集合包含的對象树灶,并根據(jù)操作符的不同返回不同的類型纤怒,返回值以NSNumber為主。
- 數(shù)組操作符:根據(jù)操作符的條件天通,將符合條件的對象包含在數(shù)組中返回泊窘。
- 嵌套操作符:處理集合對象中嵌套其他集合對象的情況,返回結(jié)果也是一個集合對象像寒。
1. 集合操作符
集合操作符處理 NSArray和 NSSet 及其子類這樣的集合對象烘豹,并根據(jù)不同的操作符返回不同類型的對象,返回值一般都是 NSNumber诺祸。
- @avg 用來計算集合中 right keyPath 指定的屬性的平均值携悯。
//@avg:平均值
NSNumber *avg = [array valueForKeyPath:@"@avg.streetNumber"];
NSLog(@"%@",avg);
- @count 用來計算集合中對象的數(shù)量。備注:@count 操作符比較特殊筷笨,它不需要寫 right keyPath憔鬼,即使寫了也會被忽略。
//@count:集合里對象的數(shù)量
NSNumber *count = [array valueForKeyPath:@"@count"];
NSLog(@"%@",count);
- @sum 用來計算集合中 right keyPath 指定的屬性的總和胃夏。
//@sum:總和
NSNumber *sum = [array valueForKeyPath:@"@sum.streetNumber"];
NSLog(@"%@",sum);
- @max 用來查找集合中 right keyPath 指定的屬性的最大值轴或。
//@max:最大值
NSNumber *max = [array valueForKeyPath:@"@max.cityNumber"];
NSLog(@"%@",max);
- @min 用來查找集合中 right keyPath 指定的屬性的最小值。
//@min:最小值
NSNumber *min = [array valueForKeyPath:@"@min.cityNumber"];
NSLog(@"%@",min);
備注:@max 和 @min 在進行判斷時仰禀,都是通過調(diào)用 compare: 方法進行判斷照雁,所以可以通過重寫該方法對判斷過程進行控制。
2. 數(shù)組操作符(因為返回的是數(shù)組答恶,所以叫數(shù)組操作符)
- @unionOfObjects 將集合中的所有對象的同一個屬性放在數(shù)組中返回囊榜。這個和直接寫屬性好像沒區(qū)別。
NSArray *city = [array valueForKeyPath:@"@unionOfObjects.city"];
NSLog(@"%@",city);
NSArray *cityArray = [array valueForKeyPath:@"city"];
NSLog(@"%@",cityArray);
- @distinctUnionOfObjects 將集合中對象的屬性進行去重并返回亥宿。
NSArray *streetNumberArr = [array valueForKeyPath:@"@distinctUnionOfObjects.streetNumber"];
NSLog(@"%@",streetNumberArr);
3. 嵌套操作符
嵌套操作符是對集合里的集合進行操作,比如數(shù)組里面的數(shù)組砂沛。
NSMutableArray *array1 = [NSMutableArray array];
for ( int i = 0 ; i < 5 ; i++ ) {
Address *address = [[Address alloc] init];
NSString *cityStr = [NSString stringWithFormat:@"city-%d",i];
//批量賦值
NSDictionary *dic = @{
@"city":cityStr,
@"street":@"street",
@"cityNumber":@(i),
@"streetNumber":@101
};
[address setValuesForKeysWithDictionary:dic];
[array1 addObject:address];
}
NSMutableArray *array2 = [NSMutableArray array];
for ( int i = 3 ; i < 9 ; i++ ) {
Address *address = [[Address alloc] init];
NSString *cityStr = [NSString stringWithFormat:@"city-%d",i];
//批量賦值
NSDictionary *dic = @{
@"city":cityStr,
@"street":@"street",
@"cityNumber":@(i),
@"streetNumber":@101
};
[address setValuesForKeysWithDictionary:dic];
[array2 addObject:address];
}
NSArray *array = @[array1,array2];
- @unionOfArrays 是用來操作集合內(nèi)部的集合烫扼,將所有right keyPath對應(yīng)的屬性放在一個數(shù)組中返回。
NSArray *result = [array valueForKeyPath:@"@unionOfArrays.city"];
- @distinctUnionOfArrays 是用來操作集合內(nèi)部的集合對象碍庵,將所有right keyPath對應(yīng)的對象放在一個數(shù)組中映企,并進行排重悟狱。
NSArray *result = [array valueForKeyPath:@"@distinctUnionOfArrays.city"];
- @distinctUnionOfSets 是用來操作集合內(nèi)部的集合對象,將所有right keyPath對應(yīng)的對象放在一個set中堰氓,并進行排重挤渐。
NSSet *result = [array valueForKeyPath:@"@distinctUnionOfSets .city"];
4. 小技巧
如果你想對集合里裝的對象直接進行操作,可以將 right keyPath 直接寫為 self双絮。比如集合里裝的是NSNumber對象浴麻,如下:
NSArray *numbers = @[@1,@1,@3,@5];
NSNumber *sum = [numbers valueForKeyPath:@"@distinctUnionOfObjects.self"];
NSNumber *avg = [numbers valueForKeyPath:@"@avg.self"];
KVC對數(shù)值和結(jié)構(gòu)體型屬性的支持
KVC可以自動的將數(shù)值或結(jié)構(gòu)體型的數(shù)據(jù)打包或解包成NSNumber或NSValue對象,以達(dá)到適配的目的囤攀。
舉個例子软免,Person類有個NSInteger類型的age屬性,如下:
// Person.m
#import "Person.h"
@interface Person ()
@property (nonatomic,assign) NSInteger age;
@end
@implementation Person
@end
// Person.m
#import "Person.h"
@interface Person ()
@property (nonatomic,assign) NSInteger age;
@end
@implementation Person
@end
1. 修改值
我們通過KVC技術(shù)使用如下方式設(shè)置age屬性的值:
[[person setValue:[NSNumber numberWithInteger:5] forKey:@"age"];
我們賦給age的是一個NSNumber對象焚挠,KVC會自動的將NSNumber對象轉(zhuǎn)換成NSInteger對象膏萧,然后再調(diào)用相應(yīng)的訪問器方法設(shè)置age的值。
2. 獲取值
同樣蝌衔,以如下方式獲取age屬性值:
NSNumber *age = [person valueForKey:@"age"];
這時榛泛,會以NSNumber的形式返回age的值。
3. 注意點
我們不能直接將基本數(shù)據(jù)類型通過KVC賦值噩斟,需要把數(shù)據(jù)轉(zhuǎn)成NSNumber或NSValue類型傳入曹锨。
可以使用NSNumber的數(shù)據(jù)類型有:
+ (NSNumber*)numberWithChar:(char)value;
+ (NSNumber*)numberWithUnsignedChar:(unsignedchar)value;
+ (NSNumber*)numberWithShort:(short)value;
+ (NSNumber*)numberWithUnsignedShort:(unsignedshort)value;
+ (NSNumber*)numberWithInt:(int)value;
+ (NSNumber*)numberWithUnsignedInt:(unsignedint)value;
+ (NSNumber*)numberWithLong:(long)value;
+ (NSNumber*)numberWithUnsignedLong:(unsignedlong)value;
+ (NSNumber*)numberWithLongLong:(longlong)value;
+ (NSNumber*)numberWithUnsignedLongLong:(unsignedlonglong)value;
+ (NSNumber*)numberWithFloat:(float)value;
+ (NSNumber*)numberWithDouble:(double)value;
+ (NSNumber*)numberWithBool:(BOOL)value;
+ (NSNumber*)numberWithInteger:(NSInteger)valueNS_AVAILABLE(10_5,2_0);
+ (NSNumber*)numberWithUnsignedInteger:(NSUInteger)valueNS_AVAILABLE(10_5,2_0);
可以使用NSValue的數(shù)據(jù)類型有:
+ (NSValue*)valueWithCGPoint:(CGPoint)point;
+ (NSValue*)valueWithCGSize:(CGSize)size;
+ (NSValue*)valueWithCGRect:(CGRect)rect;
+ (NSValue*)valueWithCGAffineTransform:(CGAffineTransform)transform;
+ (NSValue*)valueWithUIEdgeInsets:(UIEdgeInsets)insets;
+ (NSValue*)valueWithUIOffset:(UIOffset)insets;
NSValue主要用于處理結(jié)構(gòu)體型的數(shù)據(jù),任何結(jié)構(gòu)體都是可以轉(zhuǎn)化成NSValue對象的亩冬,包括其它自定義的結(jié)構(gòu)體艘希。
屬性驗證
在調(diào)用 KVC 前可以先進行驗證 key(keyPath) 和 value 的正確性,驗證通過下面兩個方法進行硅急。驗證方法默認(rèn)實現(xiàn)返回 YES覆享,可以通過重寫對應(yīng)的方法修改驗證邏輯。
注意:驗證方法需要我們手動調(diào)用营袜,并不會在進行 KVC 的過程中自動調(diào)用撒顿。
//key方法
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue forKey:(NSString *)inKey error:(out NSError **)outError;
//keyPath方法
- (BOOL)validateValue:(inout id _Nullable * _Nonnull)ioValue
forKeyPath:(NSString *)inKeyPath error:(out NSError **)outError;
主動去調(diào)用驗證方法:
Person *person = [[Person alloc] init];
NSError *error;
NSString *name = @"xds";
if (![person validateValue:&name forKey:@"name" error:&error]) {
NSLog(@"%@", error);
}
單獨驗證
KVC還支持對單獨屬性做驗證,可以通過定義validate<Key>:error:格式的方法荚板,并在方法內(nèi)部實現(xiàn)驗證代碼凤壁。在編寫KVC驗證代碼的時候,應(yīng)該先查找屬性有沒有自定義validate方法跪另,然后再查找validateValue:方法拧抖,如果有則調(diào)用自己實現(xiàn)的方法,如果兩個方法都沒有實現(xiàn)則默認(rèn)返回YES免绿。
- (BOOL)validateName:(id *)ioValue error:(NSError * __autoreleasing *)outError{
if ((*ioValue ** nil) || ([(NSString *)*ioValue length] < 2)) {
if (outError != NULL) {
*outError = [NSError errorWithDomain:PersonErrorDomain
code:PersonInvalidNameCode
userInfo:@{ NSLocalizedDescriptionKey
: @"Name too short" }];
}
return NO;
}
return YES;
}
注意:這里 validateName 是 validate + 屬性名稱 組合而來的唧席。
KVC 的搜索規(guī)則
在學(xué)習(xí)KVC的搜索規(guī)則前,要先弄明白一個屬性的作用,這個屬性在搜索過程中起到很重要的作用淌哟。這個屬性表示是否允許讀取實例變量的值迹卢,如果為YES則在KVC查找的過程中,從內(nèi)存中讀取屬性實例變量的值徒仓。
@property (class, readonly) BOOL accessInstanceVariablesDirectly;
在KVC的實現(xiàn)中腐碱,依賴setter和getter的方法實現(xiàn),所以方法命名應(yīng)該符合蘋果要求的規(guī)范掉弛,否則會導(dǎo)致KVC失敗症见。
基礎(chǔ)Getter搜索模式(valueForKey:/valueForKeyPath:)
- 1.首先按get<Key>、<key>狰晚、is<Key>的順序查找getter方法筒饰,找到直接調(diào)用。
- 若方法的返回結(jié)果類型為是一個對象指針壁晒,則直接返回結(jié)果瓷们;
- 若類型為能夠轉(zhuǎn)化為NSNumber的基本數(shù)據(jù)類型,轉(zhuǎn)換為NSNumber后返回秒咐;
- 否則谬晕,轉(zhuǎn)換為NSValue返回。
- 2.上面的getter沒有找到携取,查找countOf<Key>攒钳、objectIn<Key>AtIndex:、<Key>AtIndexes格式的方法雷滋。
如果countOf<Key>和另外兩個方法中的一個找到不撑,那么就會返回一個可以響應(yīng)NSArray所有方法的集合代理(collection proxy object)。發(fā)送給這個代理集合(collection proxy object)的NSArray消息方法晤斩,就會以countOf<Key>焕檬、objectIn<Key>AtIndex:、<Key>AtIndexes這幾個方法組合的形式調(diào)用澳泵。如果receiver的類實現(xiàn)了get<Key>:range:方法实愚,給方法也會用于性能優(yōu)化。 - 3.還沒查到兔辅,那么查找countOf<Key>腊敲、enumeratorOf<Key>、memberOf<Key>:格式的方法维苔。如果這三個方法都找到碰辅,那么就返回一個可以響應(yīng)NSSet所有方法的集合代理(collection proxy object)。發(fā)送給這個代理集合(collection proxy object)的NSSet消息方法介时,就會以countOf<Key>乎赴、enumeratorOf<Key>忍法、memberOf<Key>:組合的形式調(diào)用。
- 4.還是沒查到榕吼,那么如果類方法accessInstanceVariablesDirectly返回YES,那么按_<key>勉失,_is<Key>羹蚣,<key>,is<Key>(注意大小寫)的順序直接搜索實例變量乱凿。如果搜索到了顽素,則返回receiver相應(yīng)實例變量的值。返回結(jié)果的處理見步驟1徒蟆。
- 5.再沒查到胁出,調(diào)用valueForUndefinedKey:方法,報出異常段审。
總結(jié)一下:
- 先找相應(yīng)的 getter 方法(get<Key>, <key>, is<Key>, 或者 _<key>)全蝶,找到了則返回(對象類型直接返回,其它類型進行轉(zhuǎn)換)寺枉;
- 沒有找到抑淫,則尋找 NSArray 相應(yīng)的方法;
- 沒有找到姥闪,則尋找 NSSet 相應(yīng)的方法始苇;
- 如果還沒有,且 accessInstanceVariablesDirectly 類屬性返回的是 YES筐喳,則去搜索實例變量(_<key>催式、_is<Key>、<key>避归、is<Key>)荣月。如果發(fā)現(xiàn)了,則返回槐脏;
- 還沒有喉童,則轉(zhuǎn)到 valueForUndefinedKey: 方法并拋出異常。
基礎(chǔ)Setter搜索模式
這是setValue:forKey:的默認(rèn)實現(xiàn)顿天,給定輸入?yún)?shù)value和key堂氯。試圖在接收調(diào)用對象的內(nèi)部,設(shè)置屬性名為key的value牌废,通過下面的步驟:
查找set<Key>:或_set<Key>命名的setter咽白,按照這個順序,如果找到的話鸟缕,調(diào)用這個方法并將值傳進去(根據(jù)需要進行對象轉(zhuǎn)換)晶框。
如果沒有發(fā)現(xiàn)一個簡單的setter排抬,但是 accessInstanceVariablesDirectly 類屬性返回YES,則查找一個命名規(guī)則為_<key>授段、_is<Key>蹲蒲、<key>、is<Key>的實例變量侵贵。根據(jù)這個順序届搁,如果發(fā)現(xiàn)則將value賦值給實例變量。
如果沒有發(fā)現(xiàn)setter或?qū)嵗兞壳嫌瑒t調(diào)用setValue:forUndefinedKey:方法卡睦,并默認(rèn)提出一個異常,但是一個NSObject的子類可以提出合適的行為漱抓。
總結(jié):
- 先找 setter 方法(set<Key>:或_set<Key>)表锻;
- 沒找到,如果則 accessInstanceVariablesDirectly 類屬性返回的是 YES乞娄,則去查找實例變量(_<key>瞬逊、_is<Key>、<key>补胚、is<Key>)码耐,若找到,則賦值溶其;
- 沒有找到 setter 方法和實例變量骚腥,則轉(zhuǎn)到 setValue:forUndefinedKey: 方法,并拋出異常瓶逃。
KVC性能
根據(jù)上面KVC的實現(xiàn)原理束铭,我們可以看出KVC的性能并不如直接訪問屬性快,雖然這個性能消耗是微乎其微的厢绝。所以在使用KVC的時候契沫,建議最好不要手動設(shè)置屬性的setter、getter昔汉,這樣會導(dǎo)致搜索步驟變長懈万。
而且盡量不要用KVC進行集合操作,例如NSArray靶病、NSSet之類的会通,集合操作的性能消耗更大,而且還會創(chuàng)建不必要的對象娄周。
私有訪問
根據(jù)上面的實現(xiàn)原理我們知道涕侈,KVC本質(zhì)上是操作方法列表以及在內(nèi)存中查找實例變量。我們可以利用這個特性訪問類的私有變量煤辨,例如下面在.m中定義的私有成員變量和屬性裳涛,都可以通過KVC的方式訪問木张。
這個操作對readonly的屬性,@protected的成員變量端三,都可以正常訪問舷礼。如果不想讓外界訪問類的成員變量,則可以將accessInstanceVariablesDirectlygetter方法返回為NO郊闯。
Person.m文件
@interface Person(){
NSString *str;
}
@property (strong, nonatomic) NSString *testStr;
@end
testStr是私有屬性且轨,str是私有成員變量攻礼。使用KVC都可以訪問到這兩個老客。如果不想讓外界訪問到str成員變量泼菌,可以這樣做:
@implementation Person
+ (BOOL)accessInstanceVariablesDirectly{
return NO;
}
@end
這樣就訪問不到私有的成員變量了。但是還是能訪問到私有的屬性然痊,因為屬性有g(shù)etter和setter方法,KVC會先搜索訪問方法屉符,再去看accessInstanceVariablesDirectlygetter能不能訪問實例變量剧浸。
KVC 的實踐
KVC在實踐中也有很多用處,例如UITabbar或UIPageControl這樣的控件矗钟,系統(tǒng)已經(jīng)為我們封裝好了唆香,但是對于一些樣式的改變并沒有提供足夠的API,這種情況就需要我們用KVC進行操作了吨艇」可以自定義一個UITabbar對象,然后在內(nèi)部創(chuàng)建自己想要的視圖东涡,并通過layoutSubviews方法在內(nèi)部進行重新布局冯吓。然后通過KVC的方式,將UITabbarController的tabbar屬性替換為自定義的類即可疮跑。
安全性檢查
KVC存在一個問題在于组贺,因為傳入的key或keyPath是一個字符串,這樣很容易寫錯或者屬性自身修改后字符串忘記修改祖娘,這樣會導(dǎo)致Crash失尖。
可以利用iOS的反射機制來規(guī)避這個問題,通過@selector()獲取到方法的SEL渐苏,然后通過NSStringFromSelector()將SEL反射為字符串掀潮。這樣在@selector()中傳入方法名的過程中,編譯器會有合法性檢查整以,如果方法不存在或未實現(xiàn)會報黃色警告胧辽。