iOS Objective-C KVC 的常見用法
前言
在我們的開發(fā)中經(jīng)常會用到kvc
給屬性賦值或者取值操作,但是kvc
的用處遠(yuǎn)遠(yuǎn)不止這些鞍匾,下面我們就常見的一些用法做一下總結(jié)。
1. Accessing Object Properties(訪問對象屬性)
定義一個BankAccount
類,代碼如下:
@interface BankAccount : NSObject
@property (nonatomic) NSNumber* currentBalance; // An attribute
@property (nonatomic) Person* owner; // A to-one relation
@property (nonatomic) NSArray< Transaction* >* transactions; // A to-many relation
@end
1.1 set value
對于屬性我們可以直接調(diào)用它的set
方法給其設(shè)置值唬复。
- set
BankAccount *myAccount = [BankAccount alloc];
[myAccount setCurrentBalance:@(100.0)];
- setValue forKey
[myAccount setValue:@(100.0) forKey:@"currentBalance"];
- setValue forKeyPath
如果我們的Person
中還有address
屬性,address
中還有street
下梢,則可以使用該方法。
[myAccount setValue:@"oneaddress.twostreet" forKeyPath:@"owner.address.street"]
- set Value sForKeys WithDictionary
通過字典設(shè)置值
NSDictionary* dict = @{ @"currentBalance":@(100),
@"owner":personObject,
@"transactions":@[t1, t2],
};
BankAccount *myAccount = [[LGStudent alloc] init];
// 字典轉(zhuǎn)模型
[myAccount setValuesForKeysWithDictionary:dict];
*setValue forUndefinedKey
當(dāng)設(shè)置屬性值但是沒找到時會調(diào)用該方法塞蹭,默認(rèn)實現(xiàn)會觸發(fā)NSUndefinedKeyException
孽江,我們可以在子類重寫此方法以自定義方式處理請求。
- (void)setValue:(id)value forUndefinedKey:(NSString *)key{
NSLog(@"沒有這個key:%@",key);
}
- set NilValue ForKey
當(dāng)我們將非對象屬性設(shè)置為nil值時番电,符合鍵值編碼的對象會向自身發(fā)送一條setNilValueForKey:
消息岗屏。默認(rèn)實現(xiàn)會觸發(fā)NSInvalidArgumentException
,但是對象可以覆蓋此行為以代替默認(rèn)值或標(biāo)記值漱办。
- (void)setNilValueForKey:(NSString *)key{
NSLog(@"非對象屬性 %@ 設(shè)置的值是空值",key);
}
1.2 get value
- valueForKey:
NSNumber *number = [myAccount valueForKey: @"currentBalance"];
- valueForKeyPath:
NSString *street = [myAccount valueForKeyPath:@"owner.address.street"]
- dictionaryWithValuesForKeys:
NSArray *array = @[@"currentBalance",@"owner",@"transactions"];
NSDictionary *dic = [myAccount dictionaryWithValuesForKeys:array];
- valueForUndefinedKey:
如果找不到由key值對應(yīng)的屬性这刷,則該對象向自身發(fā)送一條valueForUndefinedKey:
消息。的默認(rèn)實現(xiàn)會觸發(fā)一個NSUndefinedKeyException
娩井,但是子類可以覆蓋此行為暇屋,并更優(yōu)雅地處理該情況。
- (id)valueForUndefinedKey:(NSString *)key{
NSLog(@"沒有%@ 這個key - 給你一個其他的吧,別奔潰了!",key);
return [NSObject alloc];
}
2. Accessing Collection Properties(訪問集合屬性)
- 通過
key
獲取一個可變數(shù)組
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- 通過
keyPath
獲取一個可變數(shù)組
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- 通過
key
獲取一個NSMutableSet
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
- 通過
keyPath
獲取一個NSMutableSet
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
- 通過
key
獲取一個可變的有序集
- (NSMutableOrderedSet *)mutableOrderedSetValueForKey:(NSString *)key;
- 通過
keyPath
獲取一個可變的有序集
- (NSMutableOrderedSet *)mutableOrderedSetValueForKeyPath:(NSString *)keyPath;
以上API的用處:
- 跟當(dāng)我們需要獲取一個集合屬性并希望修改它的時候洞辣,這比通過
valueForKey
獲取后再寫回去要更為有效咐刨。 - 可用于
KVO
對可變類型的集合添加Observer
,例如NSMutableArray
扬霜、NSMutableSet
定鸟、NSMutableOrderedSet
、NSMutableDictionary
等著瓶,你給它們添加KVO
時仔粥,你會發(fā)現(xiàn)當(dāng)你添加或者移除元素時并不能接收到變化。因為KVO
的本質(zhì)是系統(tǒng)監(jiān)測到某個屬性的內(nèi)存地址或常量改變時蟹但,會添加上- (void)willChangeValueForKey:(NSString *)key
和- (void)didChangeValueForKey:(NSString *)key
方法來發(fā)送通知躯泰,所以一種解決方法是手動調(diào)用者兩個方法,但是并不推薦华糖,你永遠(yuǎn)無法像系統(tǒng)一樣真正知道這個元素什么時候被改變麦向。另一種便是利用使用mutableXXXValueForKey:
了。
3. Using Collection Operators(使用集合運算符)
當(dāng)我們使用valueForKeyPath:
獲取數(shù)據(jù)時客叉,可以在鍵路徑中嵌入集合運算符诵竭。集合運算符是一小部分關(guān)鍵字之一,其后跟一個@符號兼搏,該符號指定getter
在返回數(shù)據(jù)之前應(yīng)執(zhí)行的操作以某種方式處理數(shù)據(jù)卵慰。
定義一個Person類:
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) int height;
@end
3.1 Aggregation Operators(聚合操作符)
聚合操作符有 @avg、@count佛呻、@max裳朋、@min、@sum
吓著,示例:
// @avg鲤嫡、@count送挑、@max、@min暖眼、@sum
- (void)aggregationOperator{
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
Person *p = [Person new];
NSDictionary* dict = @{
@"name":@"name",
@"age":@(18+i),
@"height":@(175 + 2*arc4random_uniform(6)),
};
[p setValuesForKeysWithDictionary:dict];
[personArray addObject:p];
}
NSLog(@"%@", [personArray valueForKey:@"height"]);
/// 平均身高
float avg = [[personArray valueForKeyPath:@"@avg.height"] floatValue];
NSLog(@"%f", avg);
/// 數(shù)量
int count = [[personArray valueForKeyPath:@"@count.height"] intValue];
NSLog(@"%d", count);
/// 身高和
int sum = [[personArray valueForKeyPath:@"@sum.height"] intValue];
NSLog(@"%d", sum);
/// 身高最大值
int max = [[personArray valueForKeyPath:@"@max.height"] intValue];
NSLog(@"%d", max);
/// 身高最小值
int min = [[personArray valueForKeyPath:@"@min.height"] intValue];
NSLog(@"%d", min);
}
打印結(jié)果:
3.2 Array Operators(數(shù)組操作符)
數(shù)組操作符有@distinctUnionOfObjects @unionOfObjects
惕耕,示例:
NSMutableArray *personArray = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
Person *p = [Person new];
NSDictionary* dict = @{
@"name":@"name",
@"age":@(18+i),
@"height":@(175 + 2*arc4random_uniform(6)),
};
[p setValuesForKeysWithDictionary:dict];
[personArray addObject:p];
}
NSLog(@"%@", [personArray valueForKey:@"height"]);
// 返回操作對象指定屬性的集合 --- 全部對象
NSArray* arr1 = [personArray valueForKeyPath:@"@unionOfObjects.height"];
NSLog(@"arr1 = %@", arr1);
// 返回操作對象指定屬性的集合 -- 去重
NSArray* arr2 = [personArray valueForKeyPath:@"@distinctUnionOfObjects.height"];
NSLog(@"arr2 = %@", arr2);
打印結(jié)果:
3.3 Nesting Operators(嵌套運算符)
嵌套嵌套運算符有 @distinctUnionOfArrays @unionOfArrays @distinctUnionOfSets
,示例:
@distinctUnionOfArrays @unionOfArrays
- (void)arrayNesting{
NSMutableArray *personArray1 = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
Person *p = [Person new];
NSDictionary* dict = @{
@"name":@"name",
@"age":@(18+i),
@"height":@(175 + 2*arc4random_uniform(6)),
};
[p setValuesForKeysWithDictionary:dict];
[personArray1 addObject:p];
}
NSMutableArray *personArray2 = [NSMutableArray array];
for (int i = 0; i < 6; i++) {
Person *p = [Person new];
NSDictionary* dict = @{
@"name":@"name",
@"age":@(18+i),
@"height":@(175 + 2*arc4random_uniform(6)),
};
[p setValuesForKeysWithDictionary:dict];
[personArray2 addObject:p];
}
// 嵌套數(shù)組
NSArray* nestArr = @[personArray1, personArray2];
// 返回全部對象到一個數(shù)組
NSArray* arr1 = [nestArr valueForKeyPath:@"@unionOfArrays.height"];
NSLog(@"arr1 = %@", arr1);
// 返回去重的對象到一個數(shù)組
NSArray* arr = [nestArr valueForKeyPath:@"@distinctUnionOfArrays.height"];
NSLog(@"arr = %@", arr);
}
打印結(jié)果:
@distinctUnionOfSets:
- (void)setNesting{
NSMutableSet *personSet1 = [NSMutableSet set];
for (int i = 0; i < 6; i++) {
Person *p = [Person new];
NSDictionary* dict = @{
@"name":@"name",
@"age":@(18+i),
@"height":@(175 + 2*arc4random_uniform(6)),
};
[p setValuesForKeysWithDictionary:dict];
[personSet1 addObject:p];
}
NSLog(@"personSet1 = %@", [personSet1 valueForKey:@"height"]);
NSMutableSet *personSet2 = [NSMutableSet set];
for (int i = 0; i < 6; i++) {
Person *p = [Person new];
NSDictionary* dict = @{
@"name":@"name",
@"age":@(18+i),
@"height":@(175 + 2*arc4random_uniform(6)),
};
[p setValuesForKeysWithDictionary:dict];
[personSet2 addObject:p];
}
NSLog(@"personSet2 = %@", [personSet2 valueForKey:@"height"]);
// 嵌套set
NSSet* nestSet = [NSSet setWithObjects:personSet1, personSet2, nil];
// 并集
NSSet* set1 = [nestSet valueForKeyPath:@"@distinctUnionOfSets.height"];
NSLog(@"set1 = %@", set1);
}
打印結(jié)果:
4. Representing Non-Object Values(表示非對象值)
當(dāng)我們通過任意一個getter
獲取值的時候诫肠,如果返回值不是對象司澎,則getter
使用此值初始化一個NSNumber
或者NSValue
對象,返回該值栋豫。類似的在使用Setter
的時候惭缰,如果我們給定的值不是對象,我們也需要將使用適當(dāng)?shù)念愋蛡魅胫祵ο罅牛缓笸ㄟ^對象存儲該數(shù)據(jù)漱受。
封裝在NSNumber
對象中的標(biāo)量類型
封裝在NSValue
中的常見結(jié)構(gòu)體類型
使用自定義的結(jié)構(gòu)體類型:
定義結(jié)構(gòu)體:
typedef struct {
float x, y, z;
} ThreeFloats;
@interface MyClass
@property (nonatomic) ThreeFloats threeFloats;
@end
給自定義結(jié)構(gòu)體賦值:
ThreeFloats floats = {1., 2., 3.};
NSValue* value = [NSValue valueWithBytes:&floats objCType:@encode(ThreeFloats)];
[myClass setValue:value forKey:@"threeFloats"];
使用KVC取出自定義結(jié)構(gòu)體的值:
NSValue* result = [myClass valueForKey:@"threeFloats"];
ThreeFloats th;
[reslut getValue:&th] ;
NSLog(@"%f - %f - %f",th.x,th.y,th.z);
5. Validating Properties(驗證屬性)
KVC支持屬性驗證的方法,分別是validateValue:forKey:error:
和validateValue:forKeyPath:error:
骡送,這個驗證方法的默認(rèn)實現(xiàn)是去收到這個驗證消息的對象(或keyPath中最后的對象)中根據(jù) key 查找是否有對應(yīng)的 validate<Key>:error: 方法實現(xiàn)昂羡,如果沒有,驗證默認(rèn)成功摔踱,返回 YES虐先。,當(dāng)存在特定屬性的驗證方法時派敷,默認(rèn)實現(xiàn)將返回調(diào)用該方法的結(jié)果蛹批。
由于特定于屬性的驗證方法通過引用接收值和錯誤參數(shù),因此驗證具有三種可能的結(jié)果:
驗證方法認(rèn)為值對象有效篮愉,并在YES不更改值或錯誤的情況下返回腐芍。
驗證方法認(rèn)為值對象無效稚照,但選擇不對其進(jìn)行更改郑叠。在這種情況下,該方法返回NO錯誤參考并將錯誤參考(如果由調(diào)用者提供)設(shè)置為NSError指示失敗原因的對象浓体。
驗證方法認(rèn)為值對象無效颠蕴,但創(chuàng)建了一個新的有效對象作為替換泣刹。在這種情況下,該方法返回犀被,YES而錯誤對象保持不變椅您。在返回之前,該方法將值引用修改為指向新值對象寡键。進(jìn)行修改時掀泳,即使值對象是可變的,該方法也總是創(chuàng)建一個新對象,而不是修改舊對象开伏。
示例代碼:
Person* person = [[Person alloc] init];
NSError* error;
NSString* name = @"John";
if (![person validateValue:&name forKey:@"name" error:&error]) {
NSLog(@"%@",error);
}