KVC /KVO的底層原理和使用場景

1 KVC(KeyValueCoding)

1.1 KVC 常用的方法

(1)賦值類方法
- (void)setValue:(nullable id)value forKey:(NSString *)key;
- (void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
- (void)setValue:(nullable id)value forUndefinedKey:(NSString *)key;
- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *, id> *)keyedValues;

(2)取值類方法
// 能取得私有成員變量的值
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;

1.2 KVC 底層實現原理

當一個對象調用setValue:forKey: 方法時,方法內部會做以下操作:
 1.判斷有沒有指定key的set方法,如果有set方法,就會調用set方法,給該屬性賦值
 2.如果沒有set方法,判斷有沒有跟key值相同且帶有下劃線的成員屬性(_key).如果有,直接給該成員屬性進行賦值
 3.如果沒有成員屬性_key,判斷有沒有跟key相同名稱的屬性.如果有,直接給該屬性進行賦值
 4.如果都沒有,就會調用 valueforUndefinedKey 和setValue:forUndefinedKey:方法

1.3 KVC 的使用場景

1.3.1 賦值

(1) KVC 簡單屬性賦值

Person *p = [[Person alloc] init];
//    p.name = @"jack";
//    p.money = 22.2;
使用setValue: forKey:方法能夠給屬性賦值,等價于直接給屬性賦值
[p setValue:@"rose" forKey:@"name"];
[p setValue:@"22.2" forKey:@"money"];

(2) KVC復雜屬性賦值

//給Person添加 Dog屬性
   Person *p = [[Person alloc] init];
   p.dog = [[Dog alloc] init];
  // p.dog.name = @"阿黃";

1)setValue: forKeyPath: 方法的使用
  //修改p.dog 的name 屬性
    [p.dog setValue:@"wangcai" forKeyPath:@"name"];
    [p setValue:@"阿花" forKeyPath:@"dog.name"];

2)setValue: forKey: 錯誤用法
    [p setValue:@"阿花" forKey:@"dog.name"];
    NSLog(@"%@", p.dog.name);

3)直接修改私有成員變量
[p setValue:@"旺財" forKeyPath:@"_name"];

(3) 添加私有成員變量

Person 類中添加私有成員變量_age
[p setValue:@"22" forKeyPath:@"_age"];

1.3.2 字典轉模型

(1)簡單的字典轉模型
 +(instancetype)videoWithDict:(NSDictionary *)dict
{
    JLVideo *videItem = [[JLVideo alloc] init];
    //以前
//    videItem.name = dict[@"name"];
//    videItem.money = [dict[@"money"] doubleValue] ;
    
    //KVC,使用setValuesForKeysWithDictionary:方法,該方法默認根據字典中每個鍵值對,調用setValue:forKey方法
    // 缺點:字典中的鍵值對必須與模型中的鍵值對完全對應,否則程序會崩潰
    [videItem setValuesForKeysWithDictionary:dict];
    return videItem;
}

(2)復雜的字典轉模型
注意:復雜字典轉模型不能直接通過KVC 賦值,KVC只能在簡單字典中使用,比如:
    NSDictionary *dict = @{
                       @"name" : @"jack",
                       @"money": @"22.2",
                       @"dog" : @{
                               @"name" : @"wangcai",
                               @"money": @"11.1",

                               }

                       };
   JLPerson *p = [[JLPerson alloc]init]; // p是一個模型對象
   [p setValuesForKeysWithDictionary:dict];
內部轉換原理:
//    [p setValue:@"jack" forKey:@"name"];
//    [p setValue:@"22.2" forKey:@"money"];
//    [p setValue:@{
//                  @"name" : @"wangcai",
//                  @"money": @"11.1",
//
//                  } forKey:@"dog"]; //給 dog賦值一個字典肯定是不對的

(3)KVC解析復雜字典的正確步驟
   NSDictionary *dict = @{
                       @"name" : @"jack",
                       @"money": @"22.2",
                       @"dog" : @{
                               @"name" : @"wangcai",
                               @"price": @"11.1",
                               },
                       //人有好多書
                       @"books" : @[
                               @{
                                   @"name" : @"5分鐘突破iOS開發",
                                   @"price" : @"19.8"
                                   },
                               @{
                                   @"name" : @"3分鐘突破iOS開發",
                                   @"price" : @"24.8"
                                   },
                               @{
                                   @"name" : @"1分鐘突破iOS開發",
                                   @"price" : @"29.8"
                                   }
                               ]
                       };

    XMGPerson *p = [[XMGPerson alloc] init];
     p.dog = [[XMGDog alloc] init];
    [p.dog setValuesForKeysWithDictionary:dict[@"dog"]];
    
    //保存模型的可變數組
    NSMutableArray *arrayM = [NSMutableArray array];
    
    for (NSDictionary *dict in dict[@"books"]) {
        //創建模型
        Book *book = [[Book alloc] init];
        //KVC
        [book setValuesForKeysWithDictionary:dict];
        //將模型保存
        [arrayM addObject:book];
    }
   
         p.books = arrayM;

備注:
    (1)當字典中的鍵值對很復雜,不適合用KVC;
    (2)服務器返還的數據,你可能不會全用上,如果在模型一個一個寫屬性非常麻煩,所以不建議使用KVC字典轉模型

1.3.3 取值

(1) 模型轉字典

 Person *p = [[Person alloc]init];
 p.name = @"jack";
 p.money = 11.1;
 //KVC取值
 NSLog(@"%@ %@", [p valueForKey:@"name"], [p valueForKey:@"money"]);

 //模型轉字典, 根據數組中的鍵獲取到值,然后放到字典中
 NSDictionary *dict = [p dictionaryWithValuesForKeys:@[@"name", @"money"]];
 NSLog(@"%@", dict);

(2) 訪問數組中元素的屬性值

Book *book1 = [[Book alloc] init];
book1.name = @"5分鐘突破iOS開發";
book1.price = 10.7;

Book *book2 = [[Book alloc] init];
book2.name = @"4分鐘突破iOS開發";
book2.price = 109.7;

Book *book3 = [[Book alloc] init];
book3.name = @"1分鐘突破iOS開發";
book3.price = 1580.7;

// 如果valueForKeyPath:方法的調用者是數組,那么就是去訪問數組元素的屬性值
// 取得books數組中所有Book對象的name屬性值,放在一個新的數組中返回
    NSArray *books = @[book1, book2, book3];
    NSArray *names = [books valueForKeyPath:@"name"];
    NSLog(@"%@", names);

//訪問屬性數組中元素的屬性值
Person *p = [[Person alloc]init];
p.books = @[book1, book2, book3];
NSArray *names = [p valueForKeyPath:@"books.name"];
NSLog(@"%@", names);

2 KVO (Key Value Observing)

2.1 KVO 的底層實現原理

(1)KVO 是基于 runtime 機制實現的
(2)當一個對象(假設是person對象,對應的類為 JLperson)的屬性值age發生改變時,系統會自動生成一個繼承自JLperson的類NSKVONotifying_JLPerson,在這個類的 setAge 方法里面調用
    [super setAge:age];
    [self willChangeValueForKey:@"age"];
    [self didChangeValueForKey:@"age"];
 三個方法,而后面兩個方法內部會主動調用
 -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context方法,在該方法中可以拿到屬性改變前后的值.

2.2 KVO的作用

  • 作用:能夠監聽某個對象屬性值的改變
// 利用KVO監聽p對象name 屬性值的改變
    Person *p = [[XMGPerson alloc] init];
    p.name = @"jack";
    
   /* 對象p添加一個觀察者(監聽器)
     Observer:觀察者(監聽器)
     KeyPath:屬性名(需要監聽哪個屬性)
     */
    [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew  | NSKeyValueObservingOptionOld context:@"123"];
    
 /**
 *  利用KVO 監聽到對象屬性值改變后,就會調用這個方法
 *
 *  @param keyPath 哪一個屬性被改了
 *  @param object  哪一個對象的屬性被改了
 *  @param change  改成什么樣了
 */
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context
{
    // NSKeyValueChangeNewKey == @"new"
    NSString *new = change[NSKeyValueChangeNewKey];
    // NSKeyValueChangeOldKey == @"old"
    NSString *old = change[NSKeyValueChangeOldKey];
    
    NSLog(@"%@-%@",new,old);
}
  

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

推薦閱讀更多精彩內容