美文网首页
浅谈KVC的本质及原理

浅谈KVC的本质及原理

作者: 小心韩国人 | 来源:发表于2019-03-04 17:17 被阅读0次

    KVC全称是Key-Value Coding,俗称"键值编码",可以通过一个key访问某个属性.

    常见的API有:

    • -(void)setValue:(nullable id)value forKey:(NSString *)key;
    • -(void)setValue:(nullable id)value forKeyPath:(NSString *)keyPath;
    • - (nullable id)valueForKey:(NSString *)key;
    • - (nullable id)valueForKeyPath:(NSString *)keyPath;
    ❓通过KVC能否触发KVO?

    我们通过这个问题开始研究KVC的本质?
    我们创建一个HHPerson类,添加一个age属性,然后给这个age属性添加一个KVO,使用KVC修改age的值:

    HHPerson *person = [[HHPerson alloc]init];
    HHObserver *observer = [[HHObserver alloc]init];
    [person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    [person setValue:@10 forKey:@"age"];
    
    =================打印结果=================
    
    2019-03-04 15:23:42.197654+0800 KVC_01[9905:1115065] age---{
        kind = 1;
        new = 10;
        old = 0;
    }
    

    可以看见,KVO触发了,之前我们讲过KVO的本质,知道触发KVO的关键在于重写了setter方法,既然KVC也能触发KVO,那就说明KVC也调用了setAge:方法,我们重写setAge:方法,打印一句话看看:

    - (void)setAge:(int)age{
        _age = age;
        NSLog(@"setAge方法被调用");
    }
    
    =================打印结果=================
    
    2019-03-04 15:28:24.318732+0800 KVC_01[9927:1116525] setAge方法被调用
    2019-03-04 15:28:24.319088+0800 KVC_01[9927:1116525] age---{
        kind = 1;
        new = 10;
        old = 0;
    }
    

    可以看到KVC访问属性的时候,的确调用了setAge:方法.那么setVlaue:forKey:是如何设值的呢?
    我用一张图来演示setVlaue:forKey:的执行顺序:

    KVC赋值流程

    执行步骤:
    1:setVlaue:forKey:首先查找setKey:方法,如果有就执行;如果没有再去查找_setKey:方法.
    2:如果没有找到_setKey:方法,就去查看accessInstanceVariablesDirectly方法的返回值,这个方法的意思是:是否允许直接进入对象的成员变量?,如果返回YES,就按照_key,_isKey,key,isKey的顺序查找成员变量,如果找到直接赋值;如果没找到就抛出异常NSUnknownKeyException.
    3:如果accessInstanceVariablesDirectly方法直接返回NO(默认返回YES),就直接抛出异常NSUnknownKeyException.

    我们先证明第一点,把HHPerson类中属性删掉,然后重写setAge:,_setAge:方法:

    - (void)setAge:(int)age{
        NSLog(@"setAge方法被调用");
    }
    
    - (void)_setAge:(int)age{
        NSLog(@"_setAge被调用");
    }
    

    然后使用KVC给age赋值,注意:此时HHPerson中已经没有属性age:

    HHPerson *person = [[HHPerson alloc]init];
    [person setValue:@10 forKey:@"age"];
    
    ========================打印结果====================
    
    2019-03-04 16:09:39.886100+0800 KVC_01[10105:1130147] setAge方法被调用
    

    我们把setAge:方法注释掉,再运行:

    2019-03-04 16:10:18.558353+0800 KVC_01[10115:1130494] _setAge被调用
    

    通过运行结果可以很清楚的看到,[person setValue:@10 forKey:@"age"];会先去查找setAge:方法,如果有就调用,如果没有再去查找_setAge:方法.

    接下来验证第二点,我们在HHPerson注释掉刚才的setAge:方法,然后重写accessInstanceVariablesDirectly方法,然后返回NO,运行下看看效果:

    再修改为return YES;然后添加如下成员变量:

    @interface HHPerson : NSObject
    {
        @public
        int _age;
        int _isAge;
        int age;
        int isAge;
    }
    @end
    

    在运行一下看看结果:



    有人可能会以为是成员变量顺序导致的,我们把成员变量的顺序打乱在执行看看:

    @interface HHPerson : NSObject
    {
        @public
        int age;
        int _isAge;
        int isAge;
        int _age;
    }
    @end
    

    我们把4个属性全注释掉,运行一下看看效果:


    又报NSUnknownKeyException的错误.

    我们之前讲过:如果修改一个类的成员变量,会不会触发KVO?
    答案是:不会!
    但是如果用KVC修改一个类的成员变量就会触发KVO.我们来验证一下:

    // HHPerson 中只有一个成员变量 age
    @interface HHPerson : NSObject
    {
        @public
        int age;
    //    int _isAge;
    //    int isAge;
    //    int _age;
    }
    @end
    
    // 使用 KVC 修改这个 age 的值
    HHPerson *person = [[HHPerson alloc]init];
    HHObserver *observer = [[HHObserver alloc]init];
    [person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    [person setValue:@10 forKey:@"age"];
    
    ==================打印结果====================
    
    2019-03-04 17:04:15.930503+0800 KVC_01[10255:1148495] age---{
        kind = 1;
        new = 10;
        old = 0;
    }
    

    可以看到的确触发 KVO 了.这是为什么呢?
    因为KVC赋值同样会调用willChangeValueForKey:,didChangeValueForKey:两个方法,我们在HHPerson中重写这两个方法:

    - (void)willChangeValueForKey:(NSString *)key{
        [super willChangeValueForKey:key];
        NSLog(@"willChangeValueForKey:%@",key);
    }
    
    - (void)didChangeValueForKey:(NSString *)key{
        NSLog(@"didChangeValueForKey:%@ -----begin",key);
        [super didChangeValueForKey:key];
        NSLog(@"didChangeValueForKey:%@ -----end",key);
    }
    
    
    ==================打印结果====================
    
    2019-03-04 17:08:31.074916+0800 KVC_01[10292:1150309] willChangeValueForKey:age
    2019-03-04 17:08:31.075122+0800 KVC_01[10292:1150309] didChangeValueForKey:age -----begin
    2019-03-04 17:08:31.075304+0800 KVC_01[10292:1150309] age---{
        kind = 1;
        new = 10;
        old = 0;
    }
    

    可以看到,这两个方法的确都被调用了,所以KVC给一个类的属性赋值也能触发KVO.

    以上讲的都是KVC的设值方法,接下来我们讲解一下KVC的取值方法.其实跟设值方法过程差不多,我们也用一张图表示:


    KVC取值流程

    大家可以参照上面的赋值流程验证一下,我就不重复验证了,都差不多.

    ❓KVC的赋值过程和取值过程是怎样的?原理是什么?
    答案:上面两张流程图

    相关文章

      网友评论

          本文标题:浅谈KVC的本质及原理

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