KVO和KVC

作者: 珍此良辰 | 来源:发表于2016-07-27 23:41 被阅读65次

    KVC

    • kvc全称key-value-coding(键值编码),通常是用来给某一个对象的属性进行赋值,比如有一个person类,其对外有3个属性 —— 姓名、性别和年龄,我们创建一个人 p 后可以通过点语法直接给p的属性赋值。
    // 创建person对象
    Person *p = [[Person alloc] init];
    // 给person对象属性赋值
    p.name = @"狗蛋儿";
    p.sex = @"男";
    p.age = 188;
    
    
    • 接着我们通过kvc的方式给 p 对象的属性赋值

      • 注意:因为setValue中的值是id类型,指向任何对象,所以需要将整数包装成一个对象


      [p setValue:@"狗蛋儿" forKey:@"name"];
      [p setValue:@"男" forKey:@"sex"];
      [p setValue:@188 forKey:@"age"];
      
      
    • 到这里可能有的朋友会说这样赋值麻烦而且多此一举,但是如果person这个类中有个私有的属性 —— height,只提供输出接口,那么正常情况下我们是无法改变其值的,如下:

    .h

    @interface Person : NSObject
    
    /**
     * 获取身高
     */
    - (void)getHeight;
    
    @end
    
    

    .m

    @implementation Person
    {
        NSInteger _height;  // 身高
    }
    
    - (void)getHeight {
    
        NSLog(@"身高--%ld", _height);
    }
    
    @end
    
    
    • 这个时候外界是无法访问到 _height 这个属性的,但是如果想改变其值,那么我们就可以使用KVC的方式
    
    [p setValue:@1.8f forKey:@"height"];
    
    // 获取身高
    [p getHeight];
    
    

    结果:


    身高打印结果.png
    • 除了 setValue:forKey: 这个方法外,苹果还提供了另一个方法 setValue:forKeyPath: 这两个方法对于普通属性来说是没有区别的,都可用,但是对于一些特殊的属性自然就要使用 setValue:forKeyPath: 方法了(看名字都这么长,肯定比较牛)至于牛仔哪里,肯定要试一试

      • 假如person这个类中我们又有个属性dog,Dog类中又有个属性体重,那么我们怎么通过'p'这个对象去设置狗的属性呢?
          // 初始化Dog对象
      p.dog = [[Dog alloc] init];
      // 给dog对象赋值
      [p setValue:@"旺财" forKey:@"dog.name"];
      
      

      结果:


      逐级寻找key错误演示.png
      • 从图中可以看到,如果我们使用 setValue:forKey: 这个方法,Xcode会报错说找不到dog.name这个key,想想我们在stroyboard中,如果我们控件连线出现错误,也会报相似的错误,说明了stroyboard在赋值的时候也是通过kvc的方式来操作的
      • 现在我们来试试用 setValue:forKeyPath: 方法


      [p setValue:@"旺财" forKeyPath:@"dog.name"];
          
      

      结果:


      逐级寻找key演示.png
      • 成功了,说明 setValue:forKeyPath: 方法中包含了 setValue:forKeyPath: 的方法,但是内部增加了更高级的功能 —— 内部实现:它会先去 person类中寻找有没有 dog这个key,如果有,那么会去Dog类中寻找有没有 name这个key,如果有,就给name这个key赋值,所以个人比较喜欢使用 setValue:forKeyPath这个方法
    • 不知道大家有没有注意到,前面我们使用KVC的方式给 _height 属性赋值的时候我们是这样写的

    [p setValue:@1.8f forKey:@"height"];
    
    
    • 这边有个问题 —— 我们传入的字符串key是 height,但是定义的属性则是 _height,为什么还可以给_height 赋值呢?

      • 这说明使用KVC对某个属性进行赋值时,可以不用加 _,因为KVC的查找规则是:先寻找和直接输出的字符串相同的成员变量,如果找不到再去寻找以_开头的相似的成员变量
    • 当然,KVC的用处不仅仅这点,开发中我们经常需要将字典转成模型以方便View取值,这里我们给Person 类再提供一个外部方法,在方法中实现使用KVC的方式将传入的字典转成模型

      • 当然,setValuesForKeysDictionary:这个方法只能实现比较简单的字典转模型,如果是深层次的字典,还是需要手动去实现,也可以使用第三方框架来处理(业内用的最多的应该就是MJExtension,不得不说确实好用)
    - (instancetype)initWithDict:(NSDictionary *)dict {
    
        if (self = [super init]) {
            
            [self setValuesForKeysWithDictionary:dict];
        }
        
        return self;
    }
    
    
    • 接着我们在外部调用 initWithDict: 这个方法来实现字典转模型
    // 初始化字典内容
        NSDictionary *dict = @{
                               @"name" : @"狗蛋儿",
                               @"sex" : @"男",
                               @"age" : @188,
                               };
        // 调用initWithDict:方法
        Person *p1 = [p initWithDict:dict];
        // 打印
        NSLog(@"p1.name=%@, p1.sex=%@, p1.age=%ld", p1.name, p1.sex, p1.age);
    
    

    结果:


    KVC字典转模型演示.png
    • 前面只提到赋值,但是怎么取值呢,其实很简单,苹果提供了2个取值的方法,取值时只需要将对应的key传入就可以了

      • valueForKey:
      • valueForKeyPath:
    • 总结:

      • KVC常见的2种应用场景
        • 对私有变量进行赋值
        • 字典转模型
      • 字典转模型注意点
        • 字典中的某个key一定要在模型中有对应属性
        • 一个模型中如果包含了另外的模型对象,是无法直接使用setValuesForKeysDictionary:方法转化成功的
        • 通过KVC转化模型中你的模型,也是无法直接转化成功的

    KVO

    • KVO全称key-value-observing,也就是我们常说的观察者模式,它的原理就是利用一个key来找到某个属性并监听值的改变
    • KVO的使用比较简单,大致分为3个步骤
      • 添加观察者
      • 在观察者中实现监听方法
      • 移除观察者


    1.添加观察者

        // 实例化person对象
        _p = [[Person alloc] init];
        _p.name = @"狗蛋儿";
    
        /**
         *  观察p对象的name值,如果改变则打印新值
         *
         *  @param observer 观察者
         *  @param keyPath  观察的属性
         *  @param options  观察模式
         *  @param context  额外数据
         */
         // options取值:NSKeyValueObservingOptionNew(新值), NSKeyValueObservingOptionOld(旧值)
         
        [_p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
    
    

    2.在回调中实现监听方法

    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
        
        NSLog(@"%@的值改变为%@",keyPath, change[@"new"]);
    }
    
    

    3.移除观察者(重要)

    - (void)dealloc {
        
        // 移除
        [_p removeObserver:self forKeyPath:@"name"];
    }
    
    

    结果:


    KVO演示.png
    • 那么KVO实现原理:当观察一个对象时,一个新的类会动态被创建,这个类继承自该对象原本的类,并且重写了被观察的属性的setter方法,重写的setter方法会负责在调用原setter方法之前和之后,通知所有观察对象,最后把这个对象的isa指针指向这个新创建的子类,对象就变成新创建的子类的实例,苹果为了让我们认为这个类没有被修改过,还重写了-class方法(就是原本的类),至于为什么这样做,还没弄清楚,苹果也不希望暴露太多KVO实现细节,苹果官方文档只说明:被观察对象的isa指针会指向一个中间类,而不是原来真正的类

    • KVO的缺陷:KVO是很强大的,但是苹果给的API实在是不咋地,我们只能通过重写observeValueForKeyPath:ofObject:change:context:方法获得通知,想传block、自定义方法等都不行,所以实现开发中KVO使用的情景不多,更多是用Delegate(代理)或者NotificationCenter(通知中心)代替,当然很多大神已经吊打官方KVO好多次了,有想研究了可以去搜一下相关资料

    最后附上本章参考Demo 密码: b55p

    相关文章

      网友评论

          本文标题:KVO和KVC

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