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 底層實現(xiàn)原理

當(dāng)一個對象調(diào)用setValue:forKey: 方法時,方法內(nèi)部會做以下操作:
 1.判斷有沒有指定key的set方法,如果有set方法,就會調(diào)用set方法,給該屬性賦值
 2.如果沒有set方法,判斷有沒有跟key值相同且?guī)в邢聞澗€的成員屬性(_key).如果有,直接給該成員屬性進(jìn)行賦值
 3.如果沒有成員屬性_key,判斷有沒有跟key相同名稱的屬性.如果有,直接給該屬性進(jìn)行賦值
 4.如果都沒有,就會調(diào)用 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復(fù)雜屬性賦值

//給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 字典轉(zhuǎn)模型

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

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

                               }

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

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

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

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

1.3.3 取值

(1) 模型轉(zhuǎn)字典

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

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

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

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

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

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

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

//訪問屬性數(shù)組中元素的屬性值
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 的底層實現(xiàn)原理

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

2.2 KVO的作用

  • 作用:能夠監(jiān)聽某個對象屬性值的改變
// 利用KVO監(jiān)聽p對象name 屬性值的改變
    Person *p = [[XMGPerson alloc] init];
    p.name = @"jack";
    
   /* 對象p添加一個觀察者(監(jiān)聽器)
     Observer:觀察者(監(jiān)聽器)
     KeyPath:屬性名(需要監(jiān)聽哪個屬性)
     */
    [p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew  | NSKeyValueObservingOptionOld context:@"123"];
    
 /**
 *  利用KVO 監(jiān)聽到對象屬性值改變后,就會調(diào)用這個方法
 *
 *  @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);
}
  

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容