KVC使用詳解

KVC

KVC(Key-value coding)鍵值編碼白热,就是指iOS的開發(fā)中,可以允許開發(fā)者通過Key名直接訪問對(duì)象的屬性粗卜,或者給對(duì)象的屬性賦值屋确。而不需要調(diào)用明確的存取方法。這樣就可以在運(yùn)行時(shí)動(dòng)態(tài)地訪問和修改對(duì)象的屬性续扔。而不是在編譯時(shí)確定攻臀,這也是iOS開發(fā)中的黑魔法之一。很多高級(jí)的iOS開發(fā)技巧都是基于KVC實(shí)現(xiàn)的测砂。
下面是KVC最為重要的四個(gè)方法:

- (nullable id)valueForKey:(NSString *)key;                          //直接通過Key來(lái)取值
- (void)setValue:(nullable id)value forKey:(NSString *)key;          //通過Key來(lái)設(shè)值
- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通過KeyPath來(lái)取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通過KeyPath來(lái)設(shè)值

KVC中其他比較重要的方法

+ (BOOL)accessInstanceVariablesDirectly;
//默認(rèn)返回YES茵烈,表示如果沒有找到Set<Key>方法的話,會(huì)按照_key砌些,_iskey呜投,key,iskey的順序搜索成員存璃,設(shè)置成NO就不這樣搜索
- (nullable id)valueForUndefinedKey:(NSString *)key;
//如果Key不存在仑荐,且沒有KVC無(wú)法搜索到任何和Key有關(guān)的字段或者屬性,則會(huì)調(diào)用這個(gè)方法纵东,默認(rèn)是拋出異常粘招。

- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
//和上一個(gè)方法一樣,但這個(gè)方法是設(shè)值偎球。

- (void)setNilValueForKey:(NSString *)key;
//如果你在SetValue方法時(shí)面給Value傳nil洒扎,則會(huì)調(diào)用這個(gè)方法

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
//輸入一組key,返回該組key對(duì)應(yīng)的Value辑甜,再轉(zhuǎn)成字典返回,用于將Model轉(zhuǎn)到字典袍冷。

KVC設(shè)值

KVC要設(shè)值磷醋,那么就要對(duì)象中對(duì)應(yīng)的key,KVC在內(nèi)部是按什么樣的順序來(lái)尋找key的胡诗。當(dāng)調(diào)用setValue:屬性值 forKey:@”name“的代碼時(shí)邓线,底層的執(zhí)行機(jī)制如下:

  • 程序優(yōu)先調(diào)用set:屬性值方法,代碼通過setter方法完成設(shè)置煌恢。

  • 如果沒有找到setName:方法骇陈,KVC機(jī)制會(huì)檢查+ (BOOL)accessInstanceVariablesDirectly方法有沒有返回YES,默認(rèn)該方法會(huì)返回YES瑰抵,如果你重寫了該方法讓其返回NO的話你雌,那么在這一步KVC會(huì)執(zhí)行setValue:forUndefinedKey:方法,不過一般開發(fā)者不會(huì)這么做谍憔。所以KVC機(jī)制會(huì)搜索該類里面有沒有名為的成員變量匪蝙,無(wú)論該變量是在類接口處定義,還是在類實(shí)現(xiàn)處定義习贫,也無(wú)論用了什么樣的訪問修飾符逛球,只在存在以命名的變量,KVC都可以對(duì)該成員變量賦值苫昌。

  • 如果該類即沒有set:方法颤绕,也沒有_成員變量,KVC機(jī)制會(huì)搜索_is的成員變量祟身。

  • 和上面一樣奥务,如果該類即沒有set:方法,也沒有_和_is成員變量袜硫,KVC機(jī)制再會(huì)繼續(xù)搜索和is的成員變量氯葬。再給它們賦值。

  • 如果上面列出的方法或者成員變量都不存在婉陷,系統(tǒng)將會(huì)執(zhí)行該對(duì)象的setValue:forUndefinedKey:方法帚称,默認(rèn)是拋出異常。

簡(jiǎn)單來(lái)說就是如果沒有找到Set<Key>方法的話秽澳,會(huì)按照_key闯睹,_iskey,key担神,iskey的順序搜索成員并進(jìn)行賦值操作楼吃。如果開發(fā)者想讓這個(gè)類禁用KVC里,那么重寫+(BOOL)accessInstanceVariablesDirectly方法讓其返回NO即可,這樣的話如果KVC沒有找到set:屬性名時(shí)孩锡,會(huì)直接用setValue:forUndefinedKey:方法酷宵。

KVC取值

valueForKey:的搜索方式:
1、首先按getKey浮创,key忧吟,isKey的順序查找getter方法,找到直接調(diào)用斩披。如果是BOOL、int等內(nèi)建值類型讹俊,會(huì)做NSNumber的轉(zhuǎn)換垦沉。

2、上面的getter沒找到仍劈,查找countOfKey厕倍、objectInKeyAtindex、KeyAtindexes格式的方法贩疙。如果countOfKey和另外兩個(gè)方法中的一個(gè)找到讹弯,那么就會(huì)返回一個(gè)可以響應(yīng)NSArray所有方法的代理集合的NSArray消息方法。

3这溅、還沒找到组民,查找countOfKey、enumeratorOfKey悲靴、memberOfKey格式的方法臭胜。如果這三個(gè)方法都找到,那么就返回一個(gè)可以響應(yīng)NSSet所有方法的代理集合癞尚。
4耸三、還是沒找到,如果類方法accessInstanceVariablesDirectly返回YES浇揩。那么按 _key仪壮,_isKey,key胳徽,iskey的順序搜索成員名积锅。

5、再?zèng)]找到膜廊,調(diào)用valueForUndefinedKey乏沸。

KVC使用keypath

在開發(fā)過程中,一個(gè)類的成員變量有可能是自定義類或其他的復(fù)雜數(shù)據(jù)類型爪瓜,你可以先用KVC獲取該屬性蹬跃,然后再次用KVC來(lái)獲取這個(gè)自定義類的屬性,但這樣是比較繁瑣的,對(duì)此蝶缀,KVC提供了一個(gè)解決方案丹喻,那就是鍵路徑keyPath。顧名思義翁都,就是按照路徑尋找key碍论。

- (nullable id)valueForKeyPath:(NSString *)keyPath;                  //通過KeyPath來(lái)取值
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  //通過KeyPath來(lái)設(shè)值

例子

        //Test生成對(duì)象
        Test *test = [[Test alloc] init];
        //Test1生成對(duì)象
        Test1 *test1 = [[Test1 alloc] init];
        //通過KVC設(shè)值test的"test1"
        [test setValue:test1 forKey:@"test1"];
        //通過KVC設(shè)值test的"test1的name"
        [test setValue:@"xiaoming" forKeyPath:@"test1.name"];
        //通過KVC取值age打印
        NSLog(@"test的\"test1的name\"是%@", [test valueForKeyPath:@"test1.name"]);

KVC對(duì)于keyPath是搜索機(jī)制第一步就是分離key,用小數(shù)點(diǎn).來(lái)分割key柄慰,然后再像普通key一樣按照先前介紹的順序搜索下去鳍悠。

KVC處理異常

KVC中最常見的異常就是不小心使用了錯(cuò)誤的key,或者在設(shè)值中不小心傳遞了nil的值坐搔,KVC中有專門的方法來(lái)處理這些異常藏研。

KVC處理nil異常

通常情況下,KVC不允許你要在調(diào)用setValue:屬性值 forKey:(或者keyPath)時(shí)對(duì)非對(duì)象傳遞一個(gè)nil的值概行。很簡(jiǎn)單蠢挡,因?yàn)橹殿愋褪遣荒転閚il的。如果你不小心傳了凳忙,KVC會(huì)調(diào)用setNilValueForKey:方法业踏。這個(gè)方法默認(rèn)是拋出異常,所以一般而言最好還是重寫這個(gè)方法涧卵。

KVC處理UndefinedKey異常

通常情況下勤家,KVC不允許你要在調(diào)用setValue:屬性值 forKey:(或者keyPath)時(shí)對(duì)不存在的key進(jìn)行操作。 不然艺演,會(huì)報(bào)錯(cuò)forUndefinedKey發(fā)生崩潰却紧,重寫forUndefinedKey方法避免崩潰。

KVC鍵值驗(yàn)證(Key-Value Validation)

KVC提供了驗(yàn)證Key對(duì)應(yīng)的Value是否可用的方法:(如果我們需要驗(yàn)證Value則需要重寫如下方法)

- (BOOL)validateValue:(inoutid*)ioValue forKey:(NSString*)inKey error:(outNSError**)outError;

例子

@interface Test: NSObject {
    NSUInteger _age;
}

@end

@implementation Test

- (BOOL)validateValue:(inout id  _Nullable __autoreleasing *)ioValue forKey:(NSString *)inKey error:(out NSError * _Nullable __autoreleasing *)outError {
    NSNumber *age = *ioValue;
    if (age.integerValue == 10) {
        return NO;
    }
    
    return YES;
}
@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        //Test生成對(duì)象
        Test *test = [[Test alloc] init];
        //通過KVC設(shè)值test的age
        NSNumber *age = @10;
        NSError* error;
        NSString *key = @"age";
        BOOL isValid = [test validateValue:&age forKey:key error:&error];
        if (isValid) {
            NSLog(@"鍵值匹配");
            [test setValue:age forKey:key];
        }
        else {
            NSLog(@"鍵值不匹配");
        }
        //通過KVC取值age打印
        NSLog(@"test的年齡是%@", [test valueForKey:@"age"]);
        
    }
    return 0;
}

這樣就給了我們一次糾錯(cuò)的機(jī)會(huì)胎撤。需要指出的是晓殊,KVC是不會(huì)自動(dòng)調(diào)用鍵值驗(yàn)證方法的,就是說我們?nèi)绻胍I值驗(yàn)證則需要手動(dòng)驗(yàn)證伤提。但是有些技術(shù)巫俺,比如CoreData會(huì)自動(dòng)調(diào)用。

KVC處理集合

簡(jiǎn)單集合運(yùn)算符

簡(jiǎn)單集合運(yùn)算符共有@avg肿男, @count 介汹, @max , @min 舶沛,@sum5種

@interface Book : NSObject
@property (nonatomic, copy)  NSString* name;
@property (nonatomic, assign)  CGFloat price;
@end

@implementation Book
@end


int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        Book *book1 = [Book new];
        book1.name = @"The Great Gastby";
        book1.price = 10;
        Book *book2 = [Book new];
        book2.name = @"Time History";
        book2.price = 20;
        Book *book3 = [Book new];
        book3.name = @"Wrong Hole";
        book3.price = 30;
        
        Book *book4 = [Book new];
        book4.name = @"Wrong Hole";
        book4.price = 40;
        
        NSArray* arrBooks = @[book1,book2,book3,book4];
        NSNumber* sum = [arrBooks valueForKeyPath:@"@sum.price"];
        NSLog(@"sum:%f",sum.floatValue);
        NSNumber* avg = [arrBooks valueForKeyPath:@"@avg.price"];
        NSLog(@"avg:%f",avg.floatValue);
        NSNumber* count = [arrBooks valueForKeyPath:@"@count"];
        NSLog(@"count:%f",count.floatValue);
        NSNumber* min = [arrBooks valueForKeyPath:@"@min.price"];
        NSLog(@"min:%f",min.floatValue);
        NSNumber* max = [arrBooks valueForKeyPath:@"@max.price"];
        NSLog(@"max:%f",max.floatValue);
        
    }
    return 0;
}
打印結(jié)果:
sum:100.000000
avg:25.000000
count:4.000000
min:10.000000
max:40.000000
對(duì)象運(yùn)算符

比集合運(yùn)算符稍微復(fù)雜嘹承,能以數(shù)組的方式返回指定的內(nèi)容,一共有兩種:

  • @distinctUnionOfObjects
  • @unionOfObjects

它們的返回值都是NSArray如庭,區(qū)別是前者返回的元素都是唯一的叹卷,是去重以后的結(jié)果;后者返回的元素是全集。

KVC處理字典

KVC里面還有兩個(gè)關(guān)于NSDictionary的方法:

- (NSDictionary<NSString *, id> *)dictionaryWithValuesForKeys:(NSArray<NSString *> *)keys;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

dictionaryWithValuesForKeys:是指輸入一組key骤竹,返回這組key對(duì)應(yīng)的屬性帝牡,再組成一個(gè)字典。 setValuesForKeysWithDictionary是用來(lái)修改Model中對(duì)應(yīng)key的屬性蒙揣。
KVC使用優(yōu)點(diǎn)
KVC在iOS開發(fā)中是絕不可少的利器靶溜,這種基于運(yùn)行時(shí)的編程方式極大地提高了靈活性,簡(jiǎn)化了代碼懒震,甚至實(shí)現(xiàn)很多難以想像的功能罩息。 下面列舉iOS開發(fā)中KVC的使用場(chǎng)景.

1.動(dòng)態(tài)地取值和設(shè)值:利用KVC動(dòng)態(tài)的取值和設(shè)值是最基本的用途了。
2.用KVC來(lái)訪問和修改私有變量
3.Model和字典轉(zhuǎn)換
4.修改一些控件的內(nèi)部屬性

這也是iOS開發(fā)中必不可少的小技巧个扰。眾所周知很多UI控件都由很多內(nèi)部UI控件組合而成的扣汪,但是Apple度沒有提供這訪問這些控件的API,這樣我們就無(wú)法正常地訪問和修改這些控件的樣式锨匆。
而KVC在大多數(shù)情況可下可以解決這個(gè)問題。最常用的就是個(gè)性化UITextField中的placeHolderText了冬筒。

5.快捷操作集合

KVC原理

設(shè)值過程

①.按set<Key>:恐锣、_set<Key>:順序查找對(duì)象中是否有對(duì)應(yīng)的方法

  • 找到了直接調(diào)用設(shè)值

  • 沒有找到跳轉(zhuǎn)第2步

②.判斷accessInstanceVariablesDirectly結(jié)果(判斷是否可以直接訪問成員變量

  • 為YES時(shí)按照_<key>_is<Key>舞痰、<key>土榴、is<Key>的順序查找成員變量,找到了就賦值响牛;找不到就跳轉(zhuǎn)第3步

  • 為NO時(shí)跳轉(zhuǎn)第3步

③.調(diào)用setValue:forUndefinedKey:玷禽。默認(rèn)情況下會(huì)引發(fā)一個(gè)異常,但是繼承于NSObject的子類可以重寫該方法就可以避免崩潰并做出相應(yīng)措施

image.png

取值過程

①.按照get<Key>呀打、<key>矢赁、is<Key>_<key>順序查找對(duì)象中是否有對(duì)應(yīng)的方法

  • 如果有則調(diào)用getter贬丛,執(zhí)行第5步

  • 如果沒有找到撩银,跳轉(zhuǎn)到第2步

②.查找是否有countOf<Key>objectIn<Key>AtIndex: 方法(對(duì)應(yīng)于NSArray類定義的原始方法)以及<key>AtIndexes: 方法(對(duì)應(yīng)于NSArray方法objectsAtIndexes:)

  • 如果找到其中的第一個(gè)(countOf<Key>),再找到其他兩個(gè)中的至少一個(gè)豺憔,則創(chuàng)建一個(gè)響應(yīng)所有 NSArray方法的代理集合對(duì)象额获,并返回該對(duì)象(即要么是countOf<Key> + objectIn<Key>AtIndex:,要么是countOf<Key> + <key>AtIndexes:恭应,要么是countOf<Key> + objectIn<Key>AtIndex: + <key>AtIndexes:)

  • 如果沒有找到抄邀,跳轉(zhuǎn)到第3步

③.查找名為countOf<Key>enumeratorOf<Key>memberOf<Key>這三個(gè)方法(對(duì)應(yīng)于NSSet類定義的原始方法)

  • 如果找到這三個(gè)方法昼榛,則創(chuàng)建一個(gè)響應(yīng)所有NSSet方法的代理集合對(duì)象境肾,并返回該對(duì)象

  • 如果沒有找到,跳轉(zhuǎn)到第4步

④.判斷accessInstanceVariablesDirectly

  • 為YES時(shí)按照_<key>_is<Key>准夷、<key>钥飞、is<Key>的順序查找成員變量,找到了就取值

  • 為NO時(shí)跳轉(zhuǎn)第6步

⑤.判斷取出的屬性值

  • 屬性值是對(duì)象衫嵌,直接返回

  • 屬性值不是對(duì)象读宙,但是可以轉(zhuǎn)化為NSNumber類型,則將屬性值轉(zhuǎn)化為NSNumber 類型返回

  • 屬性值不是對(duì)象楔绞,也不能轉(zhuǎn)化為NSNumber類型结闸,則將屬性值轉(zhuǎn)化為NSValue類型返回

⑥.調(diào)用valueForUndefinedKey:.默認(rèn)情況下會(huì)引發(fā)一個(gè)異常,但是繼承于NSObject的子類可以重寫該方法就可以避免崩潰并做出相應(yīng)措施

image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末酒朵,一起剝皮案震驚了整個(gè)濱河市桦锄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蔫耽,老刑警劉巖结耀,帶你破解...
    沈念sama閱讀 216,744評(píng)論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異匙铡,居然都是意外死亡图甜,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,505評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門鳖眼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)黑毅,“玉大人,你說我怎么就攤上這事钦讳】笫荩” “怎么了?”我有些...
    開封第一講書人閱讀 163,105評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵愿卒,是天一觀的道長(zhǎng)缚去。 經(jīng)常有香客問我,道長(zhǎng)掘猿,這世上最難降的妖魔是什么病游? 我笑而不...
    開封第一講書人閱讀 58,242評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮稠通,結(jié)果婚禮上衬衬,老公的妹妹穿的比我還像新娘。我一直安慰自己改橘,他們只是感情好滋尉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,269評(píng)論 6 389
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著飞主,像睡著了一般狮惜。 火紅的嫁衣襯著肌膚如雪高诺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,215評(píng)論 1 299
  • 那天碾篡,我揣著相機(jī)與錄音虱而,去河邊找鬼。 笑死开泽,一個(gè)胖子當(dāng)著我的面吹牛牡拇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播穆律,決...
    沈念sama閱讀 40,096評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼惠呼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了峦耘?” 一聲冷哼從身側(cè)響起剔蹋,我...
    開封第一講書人閱讀 38,939評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎辅髓,沒想到半個(gè)月后泣崩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,354評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡洛口,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,573評(píng)論 2 333
  • 正文 我和宋清朗相戀三年律想,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绍弟。...
    茶點(diǎn)故事閱讀 39,745評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖著洼,靈堂內(nèi)的尸體忽然破棺而出樟遣,到底是詐尸還是另有隱情,我是刑警寧澤身笤,帶...
    沈念sama閱讀 35,448評(píng)論 5 344
  • 正文 年R本政府宣布豹悬,位于F島的核電站,受9級(jí)特大地震影響液荸,放射性物質(zhì)發(fā)生泄漏瞻佛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,048評(píng)論 3 327
  • 文/蒙蒙 一娇钱、第九天 我趴在偏房一處隱蔽的房頂上張望伤柄。 院中可真熱鬧,春花似錦文搂、人聲如沸适刀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,683評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)笔喉。三九已至取视,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間常挚,已是汗流浹背作谭。 一陣腳步聲響...
    開封第一講書人閱讀 32,838評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留奄毡,地道東北人折欠。 一個(gè)月前我還...
    沈念sama閱讀 47,776評(píng)論 2 369
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像秧倾,于是被迫代替她去往敵國(guó)和親怨酝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,652評(píng)論 2 354