Key-Value Coding(鍵值編碼)

一猫态、KVC簡介

KVC提供了一套不通過訪問器方法或者屬性變量,通過Key或者KeyPath直接訪問對象屬性的機(jī)制疟游。KVC是以下技術(shù)的實現(xiàn)基礎(chǔ)KVO审洞、Core Data莱睁、Cocoa bindings、AppleScript芒澜。KVC性能略遜于訪問器和實例變量仰剿,但是靈活性高,很多時候可以簡化代碼痴晦。使用KVC需要實現(xiàn)其存取方法酥馍,相關(guān)的方法都在Objective-C的NSKeyValueCoding協(xié)議中聲明,超級父類NSObject默認(rèn)遵守該協(xié)議阅酪。KVC支持對象屬性(如NSSting)同時也指出非對象屬性(基本數(shù)據(jù)類型和結(jié)構(gòu)體旨袒,提供自動轉(zhuǎn)換數(shù)據(jù)類型)。

二术辐、KVC基本原理

首先區(qū)分兩個基本概念

名稱 內(nèi)容
Key Key是標(biāo)識對象具體屬性的字符串砚尽,相當(dāng)于對象的訪問器名稱或者變量名稱,不能包含空格辉词。
KeyPath KeyPath是指定對象一系列屬性必孤,且用.分割每個屬性的字符串。字符串序列中的每個key標(biāo)識前面對象的屬性。比如說people.address.street能夠獲取people的address屬性敷搪,然后獲取到address的street屬性兴想。

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

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

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

3、如果前2步任一成功践樱,則進(jìn)行賦值厂画。必要的話進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換。

4拷邢、如果前3步進(jìn)行失敗則調(diào)用 setValue:forUndefinedKey: 拋出NSUndefinedKeyException異常袱院。

注:方法setValue:forKey:根據(jù)指定路徑獲取屬性值,KeyPath中每一個key都進(jìn)行以上步驟瞭稼;也就是說任何一個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:執(zhí)行過程

1脐瑰、首先按照此順序匹配方法 get<Key>, <key>, or is<Key>, 如果匹配成功調(diào)用方法妖枚,返回結(jié)果廷臼。必要的話進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換。

2绝页、如果1步進(jìn)行失敗荠商,則匹配以下方法 countOf<Key>、 objectIn<Key>AtIndex: 续誉、 <key>AtIndexes:若找打其中一個莱没,則返回容器類對象。該對象調(diào)用以上方法酷鸦,會調(diào)用valueForKey:方法饰躲。(NSArray類的方法)

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

4、如果前3步失敗氨淌,而且 accessInstanceVariablesDirectly 返回YES泊愧,按照以下順序匹配實例變量_<key>, _is<Key>, <key>, or is<Key>伊磺。如果實例變量找到了,則進(jìn)行復(fù)制删咱。必要的話進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換屑埋。

5、如果前4步進(jìn)行失敗則調(diào)用 valueForUndefinedKey: 拋出NSUndefinedKeyException異常腋腮。

注:
1雀彼、方法valueForKeyPath:根據(jù)指定路徑獲取屬性值,KeyPath中每一個key都進(jìn)行以上步驟即寡;也就是說任何一個key出錯徊哑,都會拋出異常。
2聪富、如果KeyPath序列中包含了一個key是一對多的關(guān)系莺丑,而且這個key不是最后一個,那么將返回所有對象的屬性值墩蔓。例如accounts.transactions.payee將返回所有account的所有transaction的所有payee值梢莽。

//VC有一個數(shù)組屬性
@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"]);
    }
    
輸出結(jié)果
2016-09-01 17:05:57.235 KVC[3467:249694] array:(
    data1,
    data2,
    data3
)

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

說明:

1阵面、必要的話進(jìn)行數(shù)據(jù)類型轉(zhuǎn)換:KVC對應(yīng)非對象類型進(jìn)行自動數(shù)據(jù)類型轉(zhuǎn)換轻局,下文做詳細(xì)說明。
2样刷、方法accessInstanceVariablesDirectly的說明:默認(rèn)返回YES仑扑,表示對象的實例變量可以直接訪問。
3置鼻、關(guān)于NSUndefinedKeyException異常的處理镇饮,下文做詳細(xì)說明

三、異常處理

1箕母、方法valueForKey:尋找不到指定Key或者KeyPath匹配的方法或變量名稱會自動調(diào)用valueForUndefinedKey: 拋出NSUndefinedKeyException異常
2储藐、方法setValue:forKey:尋找不到指定Key或者KeyPath匹配的方法或變量名稱會自動調(diào)用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"]) {
        //返回內(nèi)容自定義
        return nil;
    }
    return nil;
}
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key
{
    if ([key isEqualToString:@"key"])
    {
        //返回內(nèi)容自定義
    }
}

四、非對象類型的處理

KVC對于基本數(shù)據(jù)類型和結(jié)構(gòu)體在底層支持自動數(shù)據(jù)類型轉(zhuǎn)換嘶是。根據(jù)相對的存取方法或者實例變量判端實際需要的值類型钙勃,選擇NSNumber 或 NSValue 進(jìn)行自動轉(zhuǎn)換。
1俊啼、NSNumber對應(yīng)的基本數(shù)據(jù)類型


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]);
    }
 
 輸出結(jié)果:
 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對應(yīng)的結(jié)構(gòu)體類型


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]);
    
輸出結(jié)果:
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、注意事項
對非對象類型的屬性設(shè)置nil空值,底層調(diào)用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
{
    //自定義內(nèi)容
    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

這個標(biāo)題就不翻譯了秕硝,英文更容易理解。

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

KVC提供一套API使得屬性值生效洲尊。使得對象有機(jī)會接受值远豺、提供默認(rèn)值、拒絕新值坞嘀、拋出錯誤原因躯护。KVC不會自動調(diào)用,需要手動調(diào)用丽涩。默認(rèn)實現(xiàn)過程:
1棺滞、調(diào)用validateValue:forKey:error:
2、在對象的方法列表中匹配validate<Key>:error:
3矢渊、如果找到則執(zhí)行并返回結(jié)果
4继准、如果未找到則返回YES,并賦值
注意:set方法中禁止調(diào)用

@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);
    }
    
輸出結(jié)果
2016-09-01 15:30:29.661 KVC[3044:197432] error:Error Domain=年齡校驗 Code=0 "年齡要大于0歲" UserInfo={NSLocalizedDescription=年齡要大于0歲}

五矮男、容器類

關(guān)于KVC在容器類中的應(yīng)用移必。容器類主要包括:NSDictionary、NSArray毡鉴、NSSet三種崔泵。關(guān)于容器類的操作方法有很多,分類整理一下
1眨补、如果作為對象的一個屬性值管削,那就作為對象屬性處理倒脓,無論Key還是KeyPath都符合前四條中說的規(guī)則撑螺;
2、就可變不可變來說崎弃,一般來說存什么取什么甘晤,但是可以根據(jù)需要獲取相應(yīng)的方法

@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]);
    }
    
輸出結(jié)果
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有點不一樣饲做,而且功常用一點

//根據(jù)指定dic設(shè)置對象屬性值线婚。使用dic的key來標(biāo)識屬性,dic的value標(biāo)識值盆均,底層調(diào)用setValue:forKey:進(jìn)行賦值塞弊。
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

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

一個常見的功能應(yīng)用,獲取網(wǎng)絡(luò)數(shù)據(jù)游沿,數(shù)據(jù)解析完畢然后賦值的時候饰抒,如果Key很多是個很麻煩的事情,但是使用setValuesForKeysWithDictionary:一行代碼搞定

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

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

注意:
1诀黍、如果dic中有未定義的key那么需要進(jìn)行異常處理袋坑,參考《三、異常處理》段落眯勾。
2枣宫、容器類比如NSArray, NSSet, NSDictionary不能包含nil值,需要使用NSNull替換(一個表示nil值的單例類)
3吃环、方法dictionaryWithValuesForKeys:和setValuesForKeysWithDictionary:會自動轉(zhuǎn)換NSNull和nil也颤,不需要過多關(guān)注。

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

Paste_Image.png

整個KeyPath以運算符為中心范咨,分為3部分故觅。左邊的路徑標(biāo)識容器類(set或者array)的訪問路徑,中間是運算符渠啊,右邊是參加運算的屬性訪問路徑输吏。

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

分類 內(nèi)容
基本運算符 @avg(平均值)贯溅、@count(數(shù)量)、@max(最大值)躲查、 @min(最小值)它浅、@sum(求和)
對象運算符 @distinctUnionOfObjects(祛同屬性值集合)、@unionOfObjects(屬性值集合)
容器運算符 @distinctUnionOfArrays()镣煮、@unionOfArrays()姐霍、@distinctUnionOfSets()

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

//VC有一個數(shù)組屬性
@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);
    }
    
輸出結(jié)果
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
)

以上問本人自己學(xué)習(xí)感悟镊折,理解并整理。更多內(nèi)容請查看官方文檔介衔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末恨胚,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子炎咖,更是在濱河造成了極大的恐慌赃泡,老刑警劉巖寒波,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異升熊,居然都是意外死亡影所,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門僚碎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猴娩,“玉大人,你說我怎么就攤上這事勺阐【碇校” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵渊抽,是天一觀的道長蟆豫。 經(jīng)常有香客問我,道長懒闷,這世上最難降的妖魔是什么十减? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮愤估,結(jié)果婚禮上帮辟,老公的妹妹穿的比我還像新娘。我一直安慰自己玩焰,他們只是感情好由驹,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著昔园,像睡著了一般蔓榄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上默刚,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天甥郑,我揣著相機(jī)與錄音,去河邊找鬼荤西。 笑死澜搅,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的皂冰。 我是一名探鬼主播店展,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼养篓,長吁一口氣:“原來是場噩夢啊……” “哼秃流!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起柳弄,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤舶胀,失蹤者是張志新(化名)和其女友劉穎概说,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嚣伐,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡糖赔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了轩端。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片放典。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖基茵,靈堂內(nèi)的尸體忽然破棺而出奋构,到底是詐尸還是另有隱情,我是刑警寧澤拱层,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布弥臼,位于F島的核電站,受9級特大地震影響根灯,放射性物質(zhì)發(fā)生泄漏径缅。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一烙肺、第九天 我趴在偏房一處隱蔽的房頂上張望纳猪。 院中可真熱鬧,春花似錦桃笙、人聲如沸兆旬。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丽猬。三九已至,卻和暖如春熏瞄,著一層夾襖步出監(jiān)牢的瞬間脚祟,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工强饮, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留由桌,地道東北人。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓邮丰,卻偏偏與公主長得像行您,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子剪廉,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容

  • 1娃循、介紹: KVC鍵值編碼在iOS中允許開發(fā)者通過 Key 直接訪問對象的屬性,或者給對象的屬性或者成員變量賦值斗蒋,...
    尋形覓影閱讀 691評論 0 5
  • KVC(Key-value coding)鍵值編碼捌斧,單看這個名字可能不太好理解笛质。其實翻譯一下就很簡單了,就是指iO...
    朽木自雕也閱讀 1,552評論 6 1
  • KVC(Key-value coding)鍵值編碼捞蚂,單看這個名字可能不太好理解妇押。其實翻譯一下就很簡單了,就是指iO...
    Fendouzhe閱讀 671評論 0 6
  • 全稱:Key Value Coding(鍵值編碼) 賦值 取值
    YANGGQ閱讀 282評論 0 0
  • KVC簡單介紹 KVC(Key-value coding)鍵值編碼姓迅,就是指iOS的開發(fā)中敲霍,可以允許開發(fā)者通過Key...
    公子無禮閱讀 1,387評論 0 6