iOS開發(fā)-KVC底層原理及實踐探究

一所计、什么是KVC省古?

KVC的全稱是Key-Value Coding奇唤,翻譯成中文是 鍵值編碼蓄氧,鍵值編碼是由NSKeyValueCoding非正式協(xié)議啟用的一種機(jī)制疟位,對象采用該協(xié)議來間接訪問其屬性。實際上版扩,KVC是對NSObject的協(xié)議擴(kuò)展废离,當(dāng)然也支持NSArray、NSDictionary礁芦、NSMutableDictionary蜻韭、NSOrderedSet、NSSet柿扣。

1. 最常用的四個方法
//直接通過Key來取值
- (nullable id)valueForKey:(NSString *)key;
//通過Key來設(shè)值
- (void)setValue:(nullable id)value forKey:(NSString *)key;

//通過KeyPath來取值
- (nullable id)valueForKeyPath:(NSString *)keyPath; 
//通過KeyPath來設(shè)值                 
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;  
2. 最簡單的使用例子

現(xiàn)在有一個銀行賬戶類BankAccount肖方,銀行賬戶有余額currentBalance屬性,還有所屬的用戶信息Person類未状。

BankAccount類

@class Person;
@class Transaction;

@interface BankAccount : NSObject
@property (nonatomic, strong) NSNumber          *currentBalance;  //當(dāng)前余額
@property (nonatomic, strong) Person            *owner;  //賬戶

@end

Person類

@interface Person : NSObject
@property (nonatomic, copy) NSString            *name;          // 名字
@property (nonatomic, copy) NSString            *age;          // 年齡

@end

我們?nèi)粘i_發(fā)中使用最多的賦值就是下面這種

    BankAccount *bankAcount = [[BankAccount alloc]init];
    Person *person = [Person new];
    person.name = @"根哥";
    person.age = @"20";
    
    bankAcount.currentBalance = @2000;
    bankAcount.owner =  person;

接下來就是KVC賦值了

    BankAccount *bankAcount = [[BankAccount alloc]init];
    Person *person = [Person new];
    bankAcount.owner =  person; //此處必須先初始化一個實例對象俯画,才可進(jìn)行賦值。
    
    [bankAcount setValue:@(200) forKey:@"currentBalance"];
    [bankAcount setValue:@"28" forKeyPath:@"owner.age"];

這個簡單的例子只是為了讓你對KVC稍微有點了解娩践。

二活翩、KVC底層原理

那么KVC是怎么通過字符串Key就可以給對象賦值的了,這就要說到KVC的賦值邏輯了翻伺。

  • 當(dāng)調(diào)用setValue:forKey:設(shè)置屬性value時材泄,其底層的執(zhí)行流程為
  1. 首先去對象里面找是否有setter方法,如果有則給成員變量key賦值吨岭。
  2. 如果沒有實現(xiàn)setter方法拉宗,則會看對象的
    + (BOOL)accessInstanceVariablesDirectly(它表示是否允許讀取實例變量的值)是否返回YES,如果返回YES辣辫,則繼續(xù)步驟3旦事,否則會調(diào)用setValue:forUndefinedKey:方法,并拋出異常急灭。
  3. 滿足2的條件下 姐浮,會查找一個命名規(guī)則為_<key>、_is<Key>葬馋、<key>卖鲤、is<Key>的實例變量。根據(jù)這個順序畴嘶,如果發(fā)現(xiàn)則將value賦值給實例變量蛋逾。
  • get的搜索規(guī)則
    get的搜索規(guī)則相對于set就有點復(fù)雜了,下面只做了解就可以了

1.通過getter方法搜索實例,例如get<Key>, <key>, is<Key>, <key>的拼接方案窗悯。按照這個順序区匣,如果發(fā)現(xiàn)符合的方法,就調(diào)用對應(yīng)的方法并拿著結(jié)果跳轉(zhuǎn)到第五步蒋院。否則亏钩,就繼續(xù)到下一步莲绰。
2.如果沒有找到簡單的getter方法,則搜索其匹配模式的方法countOf<Key>铸屉、objectIn<Key>AtIndex:钉蒲、<key>AtIndexes:。如果找到其中的第一個和其他兩個中的一個彻坛,則創(chuàng)建一個集合代理對象NSKeyValueArray顷啼,該對象響應(yīng)所有NSArray的方法并返回該對象。否則昌屉,繼續(xù)到第三步钙蒙。代理對象隨后將NSArray接收到的countOf<Key>、objectIn<Key>AtIndex:间驮、<key>AtIndexes:的消息給符合KVC規(guī)則的調(diào)用方躬厌。當(dāng)代理對象和KVC調(diào)用方通過上面方法一起工作時,就會允許其行為類似于NSArray一樣竞帽。
3.如果沒有找到NSArray簡單存取方法扛施,或者NSArray存取方法組。則查找有沒有countOf<Key>屹篓、enumeratorOf<Key>疙渣、memberOf<Key>:命名的方法。如果找到三個方法堆巧,則創(chuàng)建一個集合代理對象妄荔,該對象響應(yīng)所有NSSet方法并返回。否則谍肤,繼續(xù)執(zhí)行第四步啦租。此代理對象隨后轉(zhuǎn)換countOf<Key>、enumeratorOf<Key>荒揣、memberOf<Key>:方法調(diào)用到創(chuàng)建它的對象上篷角。實際上,這個代理對象和NSSet一起工作系任,使得其表象上看起來是NSSet恳蹲。
4.如果沒有發(fā)現(xiàn)簡單getter方法,或集合存取方法組赋除,以及接收類方法accessInstanceVariablesDirectly是返回YES的阱缓。搜索一個名為
<key>非凌、_is<Key>举农、<key>、is<Key>的實例敞嗡,根據(jù)他們的順序颁糟。如果發(fā)現(xiàn)對應(yīng)的實例航背,則立刻獲得實例可用的值并跳轉(zhuǎn)到第五步,否則棱貌,跳轉(zhuǎn)到第六步玖媚。
5.如果取回的是一個對象指針,則直接返回這個結(jié)果婚脱。如果取回的是一個基礎(chǔ)數(shù)據(jù)類型今魔,但是這個基礎(chǔ)數(shù)據(jù)類型是被NSNumber支持的,則存儲為NSNumber并返回障贸。如果取回的是一個不支持NSNumber的基礎(chǔ)數(shù)據(jù)類型错森,則通過NSValue進(jìn)行存儲并返回。
6.如果所有情況都失敗篮洁,則調(diào)用valueForUndefinedKey:方法并拋出異常涩维,這是默認(rèn)行為。但是子類可以重寫此方法袁波。

三瓦阐、應(yīng)用場景

1,用KVC中的函數(shù)來操作集合(集合主要指NSArray和NSSet,不包括NSDictionary)

上面的圖是集合運(yùn)算符的格式篷牌,主要是對象調(diào)用valueForKeyPath:方法進(jìn)行操作睡蟋。運(yùn)算符有三種:
1)簡單集合運(yùn)算符共有@avg, @count 娃磺, @max 薄湿, @min ,@sum5種

    Transaction *transaction = [[Transaction alloc]init];
    transaction.name = @"3";
    transaction.money = @23;

    Transaction *transaction1 = [[Transaction alloc]init];
    transaction1.name = @"3";
    transaction1.money = @1000;
    
    NSArray *tempArray = [NSArray arrayWithObjects:transaction,transaction1, nil];
    
    NSNumber *maxValue = [tempArray valueForKeyPath:@"@max.money"];
就是遍歷數(shù)組的每一個對象偷卧,然后對應(yīng)屬性的最大值豺瘤。

2)對象運(yùn)算@distinctUnionOfObjects返回去重后的數(shù)組,@unionOfObjects返回數(shù)組

    Transaction *transaction = [[Transaction alloc]init];
    transaction.name = @"3";
    transaction.money = @23;

    Transaction *transaction1 = [[Transaction alloc]init];
    transaction1.name = @"3";
    transaction1.money = @1000;
    
    NSArray *tempArray = [NSArray arrayWithObjects:transaction,transaction1, nil];
    
    NSNumber *avg = [tempArray valueForKeyPath:@"@max.money"];
    
    
    //返回對象中name的數(shù)組 下面兩種方式一樣
    NSArray *tempArray1 = [tempArray valueForKeyPath:@"@unionOfObjects.name"];  //[@"3", @"3"]
    NSArray *resultArray = [tempArray valueForKeyPath:@"name"];  //[@"3", @"3"]
    //    NSSet *set = [NSSet setWithArray:tempArray1]; //去重

    //@distinctUnionOfObjects將集合對象中听诸,所有Transaction對象放在一個數(shù)組中坐求,并將數(shù)組進(jìn)行去重后返回。  比unionOfObjects多一步晌梨,會去重
    NSArray *tempArray2 = [tempArray valueForKeyPath:@"@distinctUnionOfObjects.name"]; //[@"3"]

3)Array和Set嵌套操作符@distinctUnionOfArrays,@distinctUnionOfSets,@unionOfArrays
就是數(shù)組或集合的雙重嵌套桥嗤。

    NSArray *dArray = @[tempArray,tempArray];
    NSArray *resultArray2 = [dArray valueForKeyPath:@"@distinctUnionOfArrays.name"]; //[@"3"]
2. 使用KVC實現(xiàn)高階消息傳遞(就是對集合中的所有對象都執(zhí)行某個方法,跟上面的函數(shù)運(yùn)算類似)
  NSArray* stringArray = @[@"china",@"japan",@"korea"];
    NSArray* capArray = [stringArray valueForKey:@"capitalizedString"]; //執(zhí)行首字符轉(zhuǎn)大寫的操作
    for (NSString* string  in capArray) {
        NSLog(@"首字母大寫:%@",string);  //China Japan Korea
    }
    NSArray* arrCapStrLength = [stringArray valueForKeyPath:@"capitalizedString.length"];
    for (NSNumber* length  in arrCapStrLength) {
        NSLog(@"%ld",(long)length.integerValue);  5 5 5
    }

此處有個非常實用的方法,就是將字符串的首字符轉(zhuǎn)為大寫其余小寫#capitalizedString#

NSString *name = @"name";
name = name.capitalizedString;  //Name

NSString *testString = @"helloWord";
testString = testString.capitalizedString;//Helloword

3, 多值操作(model和字典互轉(zhuǎn))
  • 假設(shè)dict字典中有name仔蝌,icon 的Key泛领,XGYModel模型類中必須要有同名的name,icon屬性與之相對應(yīng)敛惊。
  • 我們使用[XGYModel setValuesForKeysWithDictionary:dict];進(jìn)行字典轉(zhuǎn)模型渊鞋。
    字典轉(zhuǎn)模型的原理:
    // enumerateKeysAndObjectsUsingBlock:遍歷字典中的所有keys和valus
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
    // 利用KVC給模型中屬性賦值,,
    // key:用來給哪個屬性
    // Value:給模型的值
    [XGYModel setValue:obj forKey:key];
}];
4. 修改內(nèi)部控件的私有屬性

比如自定義UITabbar,UIPageControl ....
//后續(xù)有待補(bǔ)充...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市锡宋,隨后出現(xiàn)的幾起案子儡湾,更是在濱河造成了極大的恐慌,老刑警劉巖执俩,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件徐钠,死亡現(xiàn)場離奇詭異,居然都是意外死亡役首,警方通過查閱死者的電腦和手機(jī)尝丐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來衡奥,“玉大人摊崭,你說我怎么就攤上這事〗苋” “怎么了呢簸?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長乏屯。 經(jīng)常有香客問我根时,道長,這世上最難降的妖魔是什么辰晕? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任蛤迎,我火速辦了婚禮,結(jié)果婚禮上含友,老公的妹妹穿的比我還像新娘替裆。我一直安慰自己,他們只是感情好窘问,可當(dāng)我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布辆童。 她就那樣靜靜地躺著,像睡著了一般惠赫。 火紅的嫁衣襯著肌膚如雪把鉴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天儿咱,我揣著相機(jī)與錄音庭砍,去河邊找鬼。 笑死混埠,一個胖子當(dāng)著我的面吹牛怠缸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播钳宪,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼揭北,長吁一口氣:“原來是場噩夢啊……” “哼概耻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起罐呼,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎侦高,沒想到半個月后嫉柴,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡奉呛,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年计螺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片瞧壮。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡登馒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咆槽,到底是詐尸還是另有隱情陈轿,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布秦忿,位于F島的核電站麦射,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏灯谣。R本人自食惡果不足惜潜秋,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胎许。 院中可真熱鬧峻呛,春花似錦、人聲如沸辜窑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽穆碎。三九已至切距,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惨远,已是汗流浹背谜悟。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留北秽,地道東北人葡幸。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像贺氓,于是被迫代替她去往敵國和親蔚叨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,713評論 2 354

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