美文网首页面试Objective-CiOS开发
KVC /KVO的底层原理和使用场景

KVC /KVO的底层原理和使用场景

作者: wg689 | 来源:发表于2016-08-21 17:55 被阅读2172次

    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);
    }
      
    

    相关文章

      网友评论

      本文标题: KVC /KVO的底层原理和使用场景

      本文链接:https://www.haomeiwen.com/subject/mokwsttx.html