KVC(Key Value Coding)是Cocoa框架爲開發者提供的很是強大的工具,簡單解釋爲:鍵值編碼。它依賴於Runtime,在OC的動態性方面發揮了重要做用。
它主要的功能在於直接經過變量名稱字符串來訪問成員變量,不論是私有的仍是共有的,這也是爲何對於OC來講沒有真正的私有變量,由於它們均可以使用KVC訪問。html
下面是KVC的一些實用場景,讀者可自行編碼嘗試。git
例如設置UITextField的placeholder顏色,常規的方法是:github
// 方式一:常規設置 _nameTextField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:@"請輸入名字" attributes:@{NSForegroundColorAttributeName : [UIColor redColor]}];
經過KVC的方式設置:數組
// 方式二:使用KVC獲取私有屬性 _nameTextField.placeholder = @"請輸入名字"; // UILabel *placeHolderLabel = [_nameTextField valueForKey:@"placeholderLabel"]; UILabel *placeHolderLabel = [_nameTextField valueForKey:@"_placeholderLabel"]; placeHolderLabel.textColor = UIColor.blackColor; [self.view addSubview:_nameTextField];
這裏經過valueForKey的方式獲取了UITextField的placeholderLabel,而後對該對象的顏色進行設置。若是獲取到的placeholderLabel爲nil,有幾種可能:安全
仍是建議使用使用方式一對屬性進行設置。站在蘋果的角度,它之因此把某些屬性設置爲私有,就是不想讓開發者進行直接修改,後續一旦蘋果對系統實現有所更改,那就會致使使用KVC獲取的內容失效。
另外,在使用setValue:forKey:的時候必定要類型統一,好比你經過key獲取到的是一個Label,卻將string設置爲了value,將會crash。
上面在使用valueForKey:方法的時候參數能夠帶下劃線( _ placeholderLabel ),也能夠不帶下劃線,它的主要區別就是若是使用了帶下劃線的key,就算類中手動實現了getter方法,也不會執行類中實現的getter方法。若是使用了不帶下劃線的,將會執行類中getter方法。setter也是如此。app
在沒有比較成熟的第三方Model解析(如Mantle)前,ORM(Object Relational Mapping)可使用KVC進行處理:框架
- (instancetype)init { return [self initWithJSONDictionary:nil]; } - (instancetype)initWithJSONDictionary:(NSDictionary *)anDictionary { if (self = [super init]) { [self setValuesForKeysWithDictionary:anDictionary]; } return self; } - (void)setValue:(id)value forUndefinedKey:(NSString *)key { NSLog(@"%@",key); } // - (void)objectRelationalMapping { NSDictionary *personInfoDictionary = @{ @"name" : @"zhangsan", @"age" : @"30", @"school" : @"Hist" }; Person *p1 = [[Person alloc] initWithJSONDictionary:personInfoDictionary]; NSLog(@"%@",p1); } @end
這裏主要使用了setValuesForKeysWithDictionary:方法。須要注意的是,須要實現setValue:forUndefinedKey:方法,由於當字典中包含的key在Person屬性中並不必定存在,若是不存在的話,就會調用setValue:forUndefinedKey:。該方法默認拋出NSUndefinedKeyException異常。因此須要對其進行重寫,避免Crash。函數
KVC除了setValue:forKey:方法,還有setValue:forKeypath:方法。具體使用以下:工具
_nameTextField.placeholder = @"請輸入名字"; [_nameTextField setValue:UIColor.blackColor forKeyPath:@"placeholderLabel.textColor"];
這裏一步操做,就完成了對placeholder顏色的變動。ui
如今Person有一個friends方法,屬性聲明以下:
@property (nonatomic, copy) NSMutableArray *friends;
此時能夠經過以下的方式進行設置friends:
NSArray *personsArray = ...; Person *zhangsan = [[Person alloc] init]; zhangsan.name = @"zhangsan"; [[zhangsan mutableArrayValueForKey:@"friends"] addObjectsFromArray:personsArray];
這樣就能夠順利將personsArray賦值給zhangsan的friends。接下來換個操做:將屬性改成
@property (nonatomic, copy) NSArray *friends;
其餘代碼保持不變。再次執行,依然會給zhangsan.friends賦值。並且沒有任何crash或者異常。因而可知,經過mutableArrayValueForKey這種方式進行處理,能夠對於不可變的集合類型,提供安全的可變訪問,即便是不可變數組,也能夠增長數組元素。
使用KVC,能夠很方便地進行一些基本的函數操做,例如:
NSMutableArray *personsArray = [[NSMutableArray alloc] initWithCapacity:5]; for (NSInteger i = 0; i < 5; i ++) { NSString *tempName = [NSString stringWithFormat:@"people%ld",(long)i]; NSDictionary *personInfoDictionary = @{ @"name" : tempName, @"age" : @(10 + i), @"school" : @"Hist" }; Person *tempPerson = [[Person alloc] initWithJSONDictionary:personInfoDictionary]; [personsArray addObject:tempPerson]; } NSNumber *count = [personsArray valueForKeyPath:@"@count"]; NSNumber *sumAge = [personsArray valueForKeyPath:@"@sum.age"]; NSNumber *avgAge = [personsArray valueForKeyPath:@"@avg.age"]; NSNumber *maxAge = [personsArray valueForKeyPath:@"@max.age"]; NSNumber *minAge = [personsArray valueForKeyPath:@"@min.age"];
其中@表示是數組特有的鍵,而不是名爲count的鍵。可使用valueForKeyPath:快速進行計算。還能夠進行更復雜的一些計算:
NSArray *array = @[@"apple", @"banner", @"apple", @"orange"]; NSLog(@"%@", [array valueForKeyPath:@"@distinctUnionOfObjects.self"]); // orange,apple,banner NSArray *array1 = @[@[@"apple", @"banner"], @[@"apple", @"orange"]]; NSLog(@"%@", [array1 valueForKeyPath:@"@unionOfArrays.self"]); // apple,banner,apple,orange NSMutableArray *personsArray1 = [[NSMutableArray alloc] initWithCapacity:5]; NSMutableArray *personsArray2 = [[NSMutableArray alloc] initWithCapacity:5]; for (NSInteger i = 0; i < 5; i ++) { NSString *tempName = [NSString stringWithFormat:@"people%ld",(long)i]; NSDictionary *personInfoDictionary = @{ @"name" : tempName, @"age" : @(10 + i), @"school" : @"Hist" }; Person *tempPerson = [[Person alloc] initWithJSONDictionary:personInfoDictionary]; i % 2 == 0 ? [personsArray1 addObject:tempPerson] : [personsArray2 addObject:tempPerson]; } NSArray *personsArray = @[personsArray1, personsArray2]; NSLog(@"%@",[[personsArray valueForKeyPath:@"@unionOfArrays.age"] valueForKeyPath:@"@sum.self"]); // 60
相似上面的代碼,可使用KVC對複雜的操做進行簡單化,而沒有必要再使用for循環或者其餘遍歷操做。
Person *person = [[Person alloc] init]; [person setValue:[UIColor redColor] forKey:@"name"];
name是string類型,可是傳入一個UIColor類型也沒有異常或者Crash產生,這也是一個潛在的問題,一旦按照string使用name,就會出現問題。所以須要對value類型與key是否匹配進行判斷。KVC提供了以下的方法:
Person *person = [[Person alloc] init]; UIColor *color = [UIColor redColor]; NSError *error = nil; BOOL isValidate = [person validateValue:&color forKey:@"name" error:&error]; if (isValidate && !error) { [person setValue:color forKey:@"name"]; }
發現依然能夠設置成功,validateValue:forKey:error:方法居然返回了YES。根據官方文檔可知,此方法的默認實現將搜索接收方的類,尋找名稱匹配validate
- (BOOL)validateName:(id *)ioValue error:(NSError **)error { if ([*ioValue isKindOfClass:[NSString class]]) { return YES; } return NO; }