天天看點

objective-c KVC機制

Objective-C支援一種更靈活的操作方式,這種方式允許以字元串形式間接操作對象的屬性,這種方式的全稱是KeyValueCoding,即鍵值編碼。

簡單的KVC

最基本的KVC是由NSKeyValueCoding協定提供支援,最基本的操作屬性的兩個方法如下。

setValue:屬性名:擷取指定屬性的值。

valueForKey:屬性名:擷取指定屬性的值。

#import <Foundation/Foundation.h>

@interface FKUser:NSObject

@property(nonatomic, copy)NSString* name;

@property(nonatomic, copy)NSString* pass;

@property(nonatomic, copy)NSDate* birth;

@end

使用KVC來設定FKUser對象的屬性,以及通路FKUser對象的屬性。

#import "FKUser.h"

int main(int argc, char* argv[]){

@autoreleasepool{

FKUser* user = [[FKUser alloc]init];

[user setValue:@"孫悟空" forKey:@"name"];

[user setValue:@"1455" forKey:@"pass"];

[user setValue:[[NSDate alloc]init] forKey:@"birth"];

NSLog(@"user的name為:%@", [user valueForKey:@"name"]);

NSLog(@"user的pass為:%@", [user valueForKey:@"pass"]);

NSLog(@"user的birth為:%@", [user valueForKey:@"birth"]);

}

}

在KVC程式設計方式中,無論調用setValue:forKey方法,還是調用valueForKey:方法,都是通過NSString對象來指定被操作屬性的,其中forKey:标簽用于傳入屬性名。

對于setValue:屬性值 forKey@"name"代碼,代碼通過setter方法完成設定。

1.程式優先考慮調用“setName:屬性值”代碼通過setter方法完成設定。

2.如果該類沒有setName:方法,KVC機制會搜尋該類名為_name的成員變量,無論該成員變量實在類接口部分定義,還是在類實作部分定義,也無論用哪個通路控制符修飾,這條KVC代碼底層實際上就是對_name成員變量指派。

3.如果該類既沒有setName:方法,也沒有定義_name成員變量,KVC機制會搜尋該類名為name的成員變量,無論該成員是在類接口部分定義,還是在類實作部分定義,也無論用哪個通路控制符修飾,這條KVC代碼底層實際就是對name成員變量指派。

4.如果上面3條都沒有找到,系統将會執行該對象的setValue:forUndefinedKey:方法。

預設的setValue:forUnderfinedKey:方法實作就是引發一個異常,這個異常将會導緻程式因為異常結束。

對于“valueForKey@"name"”代碼,底層的執行機制如下:

1.程式優先考慮調用"name",代碼來擷取該getter方法的傳回值。

2.如果該類沒有name方法,KVC機制會搜尋該類名為_name的成員變量,無論該成員變量是在類接口部分定義,還是在類實作部分定義,也無論用哪個通路控制符修飾,這條KVC代碼底層實際就是傳回_name成員變量的值。

3.如果該類既沒有name方法,也沒有定義_name成員變量,KVC機制會搜尋該類名為name的成員變量,無論該成員變量是在類接口部分定義,還是在類實作部分定義,也無論用哪個通路控制符修飾,這條KVC代碼底層實際就是傳回name成員變量的值。

4.如果上面3條都沒有找到,系統将會執行該對象的valueforUndefinedKey:方法。

預設的valueforUndefinedKey:方法實作就是引發一個異常,這個異常将會導緻程式因為異常結束。

處理不存在的key

當使用KVC方式操作屬性時,這些屬性可能并不存在——既不存在對應的setter和getter方法,也不存在對應的成員變量,KVC将會自動調用setValue:forUndefinedKey:和valueForUnderfinedKey:方法——但系統預設實作的這兩個方法僅僅隻是引發異常,并沒有進行任何特别的處理。

隻要在類的實作部分重寫setValue:forUnderfinedKey:方法,甚至不需要在類的接口聲明該方法。

Objective-C并不存在絕對隐藏的方法,即使一個方法僅僅在類實作部分定義,根本不放在類接口部分定義,程式依然可通過NSObject提供的performSelector:或performSelector:withObject:方法調用到Objective-C對象的方法。

KVC機制将會調用valueForUndefinedKey:方法,為了實作自定義行為,考慮在類實作部分重寫valueForUndefinedKey:(id)key。

此時會看到,當KVC操作并不存在的key時,KVC機制總是調用我們重寫過的方法進行處理,通過這種處理機制,可以非常友善地定制自己的處理行為。

處理nil值

當調用KVC來設定對象的屬性時,如果屬性的類型是基本類型(如int、float、double)且程式傳入了對應類型的值,那麼程式目前可以正确地進行設定。但如果我們嘗試為基本類型的屬性設定一個nil,會導緻什麼結果呢?KVC會把nil當成0,還是1?

#import <Foundation/Foundation.h>

@interface FKItem : NSObject

@property(nonatomic, copy)NSString* name;

@property(nonatomic, assign)int price;

@end

上面的程式中,嘗試将NSString類型的name屬性設定為nil,這是合法的,完全可以正常執行,但嘗試将int類型的price設定為nil時,程式就有可能引發錯誤。

上面這段提示資訊實際是由程式中的setNilValueForKey:方法所産生的。也就是說,當程式嘗試為某個屬性設定nil值時,如果該屬性并不接受nil值,那麼程式将會自動執行該對象的setNilValueForKey:方法。如果為了自行定制這個方法,同樣可通過重寫setNilValueForKey:方法來實作。

-(void)setNilValueForKey:(id)key

{

//如果嘗試将key為price的屬性設為nil

if([key isEqualToString:@"price"]){

//将該price設定為0

price = 0;

}else{

[super setNilValueForKey:key];

}

}

Key路徑

kVC除了操作對象的屬性之外,還可操作對象的"複合屬性"。所謂“複合屬性”,KVC機制将其稱為Key路徑,比如,FKOrder對象内包含一個FKItem類型的item屬性,而FKItem對象又包含了name屬性和price屬性,那麼KVC可以通過item.name、item.price這種Key路徑來支援操作FKOrder對象的name、price屬性。

KVC協定中為操作Key路徑的方法如下:

setValue:forKeyPath:根據Key路徑設定屬性值。

valueForKeyPath:根據key路徑擷取屬性值。

如下程式定義了一個FKOrder(訂單)類,該FKOrder類中包含了一個FKItem類型的屬性:

#import <Foundation/Foundation.h>

#import "FKItem.h"

@interface FKOrder:NSObject

@property(nonatomic, strong)FKItem* item;

@property(nonatomic, assign)int amount;

-(int) totalPrice;

@end

#import "FKOrder.h"

int main(int argc,char * argv[]){

@autoreleasepool{

FKOrder *order = [[FKOrder alloc]init];

[order setValue:@"12" forKey:@"amount"];

[order setValue:[[FKItem alloc] init] forKey:@"item"];

[order setValue:@"滑鼠" forKeyPath:@"item.name"];

[order setValue:[NSNumber numberWIthInt:20] forKeyPath:@"item.price"];

NSLog(@"訂單包含%@個%@,總價為:%@", [order valueForKey:@"amount"], [order valueForKeyPath:@"item.name"],

[order valueForKey:@"totalPrice"]);

}

}

iOS