Key-Value Coding(鍵值編碼)

一、KVC簡介

KVC提供了一套不通過訪問器方法或者屬性變量,通過Key或者KeyPath直接訪問對象屬性的機制。KVC是以下技術的實現基礎KVO、Core Data、Cocoa bindings、AppleScript。KVC性能略遜于訪問器和實例變量,但是靈活性高,很多時候可以簡化代碼。使用KVC需要實現其存取方法,相關的方法都在Objective-C的NSKeyValueCoding協議中聲明,超級父類NSObject默認遵守該協議。KVC支持對象屬性(如NSSting)同時也指出非對象屬性(基本數據類型和結構體,提供自動轉換數據類型)。

二、KVC基本原理

首先區分兩個基本概念

名稱 內容
Key Key是標識對象具體屬性的字符串,相當于對象的訪問器名稱或者變量名稱,不能包含空格。
KeyPath KeyPath是指定對象一系列屬性,且用.分割每個屬性的字符串。字符串序列中的每個key標識前面對象的屬性。比如說people.address.street能夠獲取people的address屬性,然后獲取到address的street屬性。

然后說明等的執行過程,KVC的方法從功能上分存、取兩種方法setValue:forKey:valueForKey:,以這兩個方法為代表描述執行過程。

首先setValue:forKey:的執行過程
1、首先對象方法列表中匹配方法-set<Key>:

2、如果第1步失敗而且 accessInstanceVariablesDirectly 返回YES,按照以下順序匹配實例變量_<key>, _is<Key>, <key>, or is<Key>

3、如果前2步任一成功,則進行賦值。必要的話進行數據類型轉換。

4、如果前3步進行失敗則調用 setValue:forUndefinedKey: 拋出NSUndefinedKeyException異常。

注:方法setValue:forKey:根據指定路徑獲取屬性值,KeyPath中每一個key都進行以上步驟;也就是說任何一個key出錯,都會拋出異常。

代碼2.1
@interface ViewController ()
{
    NSString *_name;
    NSString *_isName;
    NSString *name;
    NSString *isName;

}
@property (nonatomic,copy)NSString *name;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self setValue:@"zwq" forKey:@"name"];
    
    NSLog(@"_name:%@",_name);
    NSLog(@"_isName:%@",_isName);
    NSLog(@"name:%@",name);
    NSLog(@"isName:%@",isName);
    }
//可以通過以上代碼(注釋部分代碼)來驗證上述過程。    

然后是valueForKey:執行過程

1、首先按照此順序匹配方法 get<Key>, <key>, or is<Key>, 如果匹配成功調用方法,返回結果。必要的話進行數據類型轉換。

2、如果1步進行失敗,則匹配以下方法 countOf<Key>、 objectIn<Key>AtIndex: 、 <key>AtIndexes:若找打其中一個,則返回容器類對象。該對象調用以上方法,會調用valueForKey:方法。(NSArray類的方法)

3、如果前2步失敗,則匹配以下方法countOf<Key>, enumeratorOf<Key>, and memberOf<Key>:若找打其中一個,則返回容器類對象。該對象調用以上方法,會調用valueForKey:方法。
(NSSet類的方法)

4、如果前3步失敗,而且 accessInstanceVariablesDirectly 返回YES,按照以下順序匹配實例變量_<key>, _is<Key>, <key>, or is<Key>。如果實例變量找到了,則進行復制。必要的話進行數據類型轉換。

5、如果前4步進行失敗則調用 valueForUndefinedKey: 拋出NSUndefinedKeyException異常。

注:
1、方法valueForKeyPath:根據指定路徑獲取屬性值,KeyPath中每一個key都進行以上步驟;也就是說任何一個key出錯,都會拋出異常。
2、如果KeyPath序列中包含了一個key是一對多的關系,而且這個key不是最后一個,那么將返回所有對象的屬性值。例如accounts.transactions.payee將返回所有account的所有transaction的所有payee值。

//VC有一個數組屬性
@property (nonatomic,assign)NSArray *array;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //Data有一個name屬性
    Data *data1 = [[Data alloc] init];
    Data *data2 = [[Data alloc] init];
    Data *data3 = [[Data alloc] init];
    data1.name=@"data1";
    data2.name=@"data2";
    data3.name=@"data3";
    
    //self.array.name
    NSArray *arr = [NSArray arrayWithObjects:data1,data2,data3, nil];
    [self setValue:arr forKey:@"array"];
    NSLog(@"array:%@",[self valueForKeyPath:@"array.name"]);
    }
    
輸出結果
2016-09-01 17:05:57.235 KVC[3467:249694] array:(
    data1,
    data2,
    data3
)

可以仿照代碼2.1進行代碼驗證。由上邊底層執行過程不難看出:KVC性能略遜于訪問器和實例變量,但是靈活性高,視情況選擇。

說明:

1、必要的話進行數據類型轉換:KVC對應非對象類型進行自動數據類型轉換,下文做詳細說明。
2、方法accessInstanceVariablesDirectly的說明:默認返回YES,表示對象的實例變量可以直接訪問。
3、關于NSUndefinedKeyException異常的處理,下文做詳細說明

三、異常處理

1、方法valueForKey:尋找不到指定Key或者KeyPath匹配的方法或變量名稱會自動調用valueForUndefinedKey: 拋出NSUndefinedKeyException異常
2、方法setValue:forKey:尋找不到指定Key或者KeyPath匹配的方法或變量名稱會自動調用setValue:forUndefinedKey: 拋出NSUndefinedKeyException異常

//NSUndefinedKeyException如下所示
 *** Terminating app due to uncaught exception 'NSUnknownKeyException', 
 reason: '[<ViewController 0x7fd60b728690> setValue:forUndefinedKey:]: 
 this class is not key value coding-compliant for the key age.'

處理方法為重寫此二者方法

- (nullable id)valueForUndefinedKey:(NSString *)key;

- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;

方法體可為空也可自定義處理

//空處理
- (nullable id)valueForUndefinedKey:(NSString *)key
{
    return nil;
}
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key
{

}

//自定義處理
- (nullable id)valueForUndefinedKey:(NSString *)key
{
    if ([key isEqualToString:@"key"]) {
        //返回內容自定義
        return nil;
    }
    return nil;
}
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key
{
    if ([key isEqualToString:@"key"])
    {
        //返回內容自定義
    }
}

四、非對象類型的處理

KVC對于基本數據類型和結構體在底層支持自動數據類型轉換。根據相對的存取方法或者實例變量判端實際需要的值類型,選擇NSNumber 或 NSValue 進行自動轉換。
1、NSNumber對應的基本數據類型


14726339516105.jpg

例如

@property (nonatomic,assign)BOOL fail;

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSNumber *num = [NSNumber numberWithBool:0];
    NSLog(@"class:%@",[num class]);
    
    [self setValue:@"0" forKey:@"fail"];
    NSLog(@"fali:%d--class:%@",self.fail,[[self valueForKey:@"fail"] class]);
    }
 
 輸出結果:
 2016-09-01 14:27:33.401 KVC[2672:154097] class:__NSCFBoolean
 2016-09-01 14:27:33.401 KVC[2672:154097] fali:0--class:__NSCFBoolean   

2、NSValue對應的結構體類型


14726339706102.jpg

例如

@property (nonatomic,assign)CGPoint point;

    NSValue *value = [NSValue valueWithCGPoint:CGPointMake(1, 1)];
    NSLog(@"class:%@",[value class]);

    [self setValue:value forKey:@"point"];
    NSLog(@"fali:%@--class:%@",NSStringFromCGPoint(self.point) ,[[self valueForKey:@"point"] class]);
    
輸出結果:
2016-09-01 14:40:23.599 KVC[2751:163036] class:NSConcreteValue
2016-09-01 14:40:23.599 KVC[2751:163036] fali:{1, 1}--class:NSConcreteValue

3、注意事項
對非對象類型的屬性設置nil空值,底層調用setNilValueForKey:,然后拋出NSInvalidArgumentException異常
例如

 [self setValue:nil forKey:@"fail"];
 //或
 [self setValue:nil forKey:@"point"];
 
 異常:
 *** Terminating app due to uncaught exception 'NSInvalidArgumentException', 
 reason: '[<ViewController 0x7fd769484b90> setNilValueForKey]: 
 could not set nil as the value for the key fail.'
 

解決方法是重寫該方法setNilValueForKey:,方法可空也可自定義處理,例如

-(void)setNilValueForKey:(NSString *)key
{
    //自定義內容
    if ([key isEqualToString:@"fail"])
    {
        [self setValue:[NSNumber numberWithBool:0] forKey:@"fail"];
    }
    if ([key isEqualToString:@"point"])
    {
        [self setValue:[NSValue valueWithCGPoint:CGPointZero] forKey:@"point"];
    }
}

五、Key-Value Validation

這個標題就不翻譯了,英文更容易理解。

- validateValue:forKey:error:
- validateValue:forKeyPath:error:

KVC提供一套API使得屬性值生效。使得對象有機會接受值、提供默認值、拒絕新值、拋出錯誤原因。KVC不會自動調用,需要手動調用。默認實現過程:
1、調用validateValue:forKey:error:
2、在對象的方法列表中匹配validate<Key>:error:
3、如果找到則執行并返回結果
4、如果未找到則返回YES,并賦值
注意:set方法中禁止調用

@property (nonatomic,assign)NSInteger age;

-(BOOL)validateAge:(id *)ioValue error:(NSError **)outError
{
    
    if (*ioValue == nil)
    {
        // 年齡大于0歲
        [self setValue:@"0" forKey:@"age"];
        return YES;
    }
    if ([*ioValue floatValue] <= 0.0)
    {
        if (outError != NULL)
        {
            NSString *errorString = NSLocalizedStringFromTable(
                                                               @"年齡要大于0歲", @"人",
                                                               @"年齡錯誤");
            NSDictionary *userInfoDict = @{ NSLocalizedDescriptionKey : errorString };
            NSError *error = [[NSError alloc] initWithDomain:@"年齡校驗"
                                                        code:0
                                                    userInfo:userInfoDict];
            *outError = error;
        }
        return NO;
    }
    else
    {
        return YES;
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSNumber *ageNum = [NSNumber numberWithInteger:0];
    NSError *error = nil;
    [self validateValue:&ageNum forKey:@"age" error:&error];
    NSLog(@"error:%@",error);
    }
    
輸出結果
2016-09-01 15:30:29.661 KVC[3044:197432] error:Error Domain=年齡校驗 Code=0 "年齡要大于0歲" UserInfo={NSLocalizedDescription=年齡要大于0歲}

五、容器類

關于KVC在容器類中的應用。容器類主要包括:NSDictionary、NSArray、NSSet三種。關于容器類的操作方法有很多,分類整理一下
1、如果作為對象的一個屬性值,那就作為對象屬性處理,無論Key還是KeyPath都符合前四條中說的規則;
2、就可變不可變來說,一般來說存什么取什么,但是可以根據需要獲取相應的方法

@property (nonatomic,assign)NSMutableArray *mutableArray;

@property (nonatomic,assign)NSArray *array;

- (void)viewDidLoad {
    [super viewDidLoad];
        [self setValue:[NSArray arrayWithObjects:@"zwq", nil] forKey:@"array"];
    [self setValue:[NSMutableArray arrayWithObjects:@"zwq2", nil] forKey:@"mutableArray"];
    NSLog(@"不可變:%@--%@",[[self valueForKey:@"array"] class],[[self mutableArrayValueForKey:@"array"] class]);
    NSLog(@"可變:%@--%@",[[self valueForKey:@"mutableArray"] class],[[self mutableArrayValueForKey:@"mutableArray"] class]);
    }
    
輸出結果
2016-09-01 16:30:55.057 KVC[3328:231529] 不可變:__NSArrayI--NSKeyValueSlowMutableArray
2016-09-01 16:30:55.057 KVC[3328:231529] 可變:__NSArrayM--NSKeyValueSlowMutableArray

//KeyPath道理也是一樣的

3、需要單獨說的是NSDictionary跟NSArray有點不一樣,而且功常用一點

//根據指定dic設置對象屬性值。使用dic的key來標識屬性,dic的value標識值,底層調用setValue:forKey:進行賦值。
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

//獲取一組key的屬性值,然后以NSDictionary形式返回
- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;

一個常見的功能應用,獲取網絡數據,數據解析完畢然后賦值的時候,如果Key很多是個很麻煩的事情,但是使用setValuesForKeysWithDictionary:一行代碼搞定

//比如Model的屬性
@property (nonatomic,copy)NSString *name;
@property (nonatomic,copy)NSString *address;

- (void)viewDidLoad {
    [super viewDidLoad];
    //比如需要解析的數據
    NSDictionary *dic =@{@"name":@"zwq",@"address":@"地球"};
    [self setValuesForKeysWithDictionary:dic];
    NSLog(@"name:%@--address:%@",self.name,self.address);
    }
    
    輸出結果
    2016-09-01 16:42:47.898 KVC[3367:237574] name:zwq--address:地球

注意:
1、如果dic中有未定義的key那么需要進行異常處理,參考《三、異常處理》段落。
2、容器類比如NSArray, NSSet, NSDictionary不能包含nil值,需要使用NSNull替換(一個表示nil值的單例類)
3、方法dictionaryWithValuesForKeys:和setValuesForKeysWithDictionary:會自動轉換NSNull和nil,不需要過多關注。

4、容器類運算符
容器類運算是valueForKeyPath:中特殊的KeyPath,運算符跟在@符號之后,格式如下圖

Paste_Image.png

整個KeyPath以運算符為中心,分為3部分。左邊的路徑標識容器類(set或者array)的訪問路徑,中間是運算符,右邊是參加運算的屬性訪問路徑。

暫不支持自定義運算符,總體分為三種;

分類 內容
基本運算符 @avg(平均值)、@count(數量)、@max(最大值)、 @min(最小值)、@sum(求和)
對象運算符 @distinctUnionOfObjects(祛同屬性值集合)、@unionOfObjects(屬性值集合)
容器運算符 @distinctUnionOfArrays()、@unionOfArrays()、@distinctUnionOfSets()

選擇其中一個演示一下,其它的運算符同理。

//VC有一個數組屬性
@property (nonatomic,assign)NSArray *array;


- (void)viewDidLoad {
    [super viewDidLoad];
    
    //Data有一個name屬性
    Data *data1 = [[Data alloc] init];
    Data *data2 = [[Data alloc] init];
    Data *data3 = [[Data alloc] init];
    data1.name=@"data1";
    data2.name=@"data2";
    data3.name=@"data3";
    
    //self.array.name
        NSArray *arr = [NSArray arrayWithObjects:data1,data2,data1, nil];
    [self setValue:arr forKey:@"array"];

    NSArray *distinctArr = [self valueForKeyPath:@"array.@distinctUnionOfObjects.name"];
    NSLog(@"distinctArr:%@",distinctArr);
    
    NSArray *undistinctArr = [self valueForKeyPath:@"array.@unionOfObjects.name"];
    NSLog(@"undistinctArr:%@",undistinctArr);
    }
    
輸出結果
2016-09-01 17:17:59.049 KVC[3507:256556] distinctArr:(
    data1,
    data2
)
2016-09-01 17:17:59.050 KVC[3507:256556] undistinctArr:(
    data1,
    data2,
    data1
)

以上問本人自己學習感悟,理解并整理。更多內容請查看官方文檔。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容

  • 1、介紹: KVC鍵值編碼在iOS中允許開發者通過 Key 直接訪問對象的屬性,或者給對象的屬性或者成員變量賦值,...
    尋形覓影閱讀 700評論 0 5
  • KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解。其實翻譯一下就很簡單了,就是指iO...
    朽木自雕也閱讀 1,569評論 6 1
  • KVC(Key-value coding)鍵值編碼,單看這個名字可能不太好理解。其實翻譯一下就很簡單了,就是指iO...
    Fendouzhe閱讀 680評論 0 6
  • 全稱:Key Value Coding(鍵值編碼) 賦值 取值
    YANGGQ閱讀 289評論 0 0
  • KVC簡單介紹 KVC(Key-value coding)鍵值編碼,就是指iOS的開發中,可以允許開發者通過Key...
    公子無禮閱讀 1,408評論 0 6