iOS Objective-C KVC 的常見用法

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的用處:

  1. 跟當(dāng)我們需要獲取一個集合屬性并希望修改它的時候洞辣,這比通過valueForKey獲取后再寫回去要更為有效咐刨。
  2. 可用于KVO對可變類型的集合添加Observer,例如NSMutableArray扬霜、NSMutableSet定鸟、NSMutableOrderedSetNSMutableDictionary等著瓶,你給它們添加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é)果:

打印結(jié)果.jpg

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é)果:

打印結(jié)果.jpg

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é)果:

打印結(jié)果.jpg

@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é)果:

打印結(jié)果.jpg

4. Representing Non-Object Values(表示非對象值)

當(dāng)我們通過任意一個getter獲取值的時候诫肠,如果返回值不是對象司澎,則getter使用此值初始化一個NSNumber或者NSValue對象,返回該值栋豫。類似的在使用Setter的時候惭缰,如果我們給定的值不是對象,我們也需要將使用適當(dāng)?shù)念愋蛡魅胫祵ο罅牛缓笸ㄟ^對象存儲該數(shù)據(jù)漱受。

封裝在NSNumber對象中的標(biāo)量類型

NSNumber.jpg

封裝在NSValue中的常見結(jié)構(gòu)體類型

NSValue.jpg

使用自定義的結(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é)果:

  1. 驗證方法認(rèn)為值對象有效篮愉,并在YES不更改值或錯誤的情況下返回腐芍。

  2. 驗證方法認(rèn)為值對象無效稚照,但選擇不對其進(jìn)行更改郑叠。在這種情況下,該方法返回NO錯誤參考并將錯誤參考(如果由調(diào)用者提供)設(shè)置為NSError指示失敗原因的對象浓体。

  3. 驗證方法認(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);
}

5.參考資料

Apple Documentation - About Key-Value Coding

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末膀跌,一起剝皮案震驚了整個濱河市遭商,隨后出現(xiàn)的幾起案子固灵,更是在濱河造成了極大的恐慌,老刑警劉巖劫流,帶你破解...
    沈念sama閱讀 212,029評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件巫玻,死亡現(xiàn)場離奇詭異,居然都是意外死亡祠汇,警方通過查閱死者的電腦和手機仍秤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來可很,“玉大人诗力,你說我怎么就攤上這事∥铱伲” “怎么了苇本?”我有些...
    開封第一講書人閱讀 157,570評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長菜拓。 經(jīng)常有香客問我瓣窄,道長,這世上最難降的妖魔是什么纳鼎? 我笑而不...
    開封第一講書人閱讀 56,535評論 1 284
  • 正文 為了忘掉前任俺夕,我火速辦了婚禮,結(jié)果婚禮上贱鄙,老公的妹妹穿的比我還像新娘劝贸。我一直安慰自己,他們只是感情好逗宁,可當(dāng)我...
    茶點故事閱讀 65,650評論 6 386
  • 文/花漫 我一把揭開白布悬荣。 她就那樣靜靜地躺著,像睡著了一般疙剑。 火紅的嫁衣襯著肌膚如雪氯迂。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,850評論 1 290
  • 那天言缤,我揣著相機與錄音嚼蚀,去河邊找鬼。 笑死管挟,一個胖子當(dāng)著我的面吹牛轿曙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,006評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼导帝,長吁一口氣:“原來是場噩夢啊……” “哼守谓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起您单,我...
    開封第一講書人閱讀 37,747評論 0 268
  • 序言:老撾萬榮一對情侶失蹤斋荞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后虐秦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體平酿,經(jīng)...
    沈念sama閱讀 44,207評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,536評論 2 327
  • 正文 我和宋清朗相戀三年悦陋,在試婚紗的時候發(fā)現(xiàn)自己被綠了蜈彼。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,683評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡俺驶,死狀恐怖幸逆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情暮现,我是刑警寧澤还绘,帶...
    沈念sama閱讀 34,342評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站送矩,受9級特大地震影響蚕甥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜栋荸,卻給世界環(huán)境...
    茶點故事閱讀 39,964評論 3 315
  • 文/蒙蒙 一菇怀、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晌块,春花似錦爱沟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钝尸,卻和暖如春括享,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背珍促。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評論 1 266
  • 我被黑心中介騙來泰國打工铃辖, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人猪叙。 一個月前我還...
    沈念sama閱讀 46,401評論 2 360
  • 正文 我出身青樓娇斩,卻偏偏與公主長得像仁卷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子犬第,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,566評論 2 349