美文网首页iOS底层原理
小码哥底层原理笔记:KVC的原理

小码哥底层原理笔记:KVC的原理

作者: chilim | 来源:发表于2020-05-08 19:34 被阅读0次

    KVC的作用是通过一个key来访问某个属性。主要有以下四个方法:

    //赋值方法
    - (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
    - (void)setValue:(id)value forKey:(NSString *)key;
    //取值方法
    - (id)valueForKeyPath:(NSString *)keyPath;
    - (id)valueForKey:(NSString *)key; 
    

    我们新建一个简单的Person类:

    @interface Cat : NSObject
    
    @property (nonatomic, assign) int weight;
    
    @end
    
    @interface Person : NSObject
    
    @property (nonatomic, assign) int age;
    @property (nonatomic, strong) Cat *cat;
    
    @end
    
    #import "Person.h"
    
    @implementation Person
    
    - (void)setAge:(int)age{
        _age = age;
        NSLog(@"setAge: - %d",age);
    }
    
    @end
    

    当我们访问person的age时可以这样使用:

    Person *person = [[Person alloc] init];
    [person setValue:@10 forKey:@"age"];
    [person setValue:@10 forKeyPath:@"age"];
    NSLog(@"%@",person.age,);//10
    打印:10
    person.age = 10;
    NSLog(@"%@ %@", [person valueForKey:@"age"], [person valueForKeyPath:@"age"]);
    打印:10 10
    

    发现这两种方式都是10,ForKey和ForKeyPath是没区别的,但是当我们反问Cat里面的weight时则必须要使用ForKeyPath。

    [person setValue:@20 forKeyPath:@"cat.weight"];//forKey则不能这样写
    NSLog(@"%@", person.cat.weight);
    NSLog(@"%@",  [person valueForKeyPath:@"cat.weight"]);//20
    打印:20
    

    setValue:forKey:是如何赋值的

    当我们调用[person setValue:@10 forKey:@"age”];方法时,系统会依次做如下操作:

    查找Person类里面有没有setKey方法,如果没有则查找_setKey方法,如果查找到了就直接调用,如果都没有找到那么会调用accessInstanceVariablesDirectly方法,该方法是控制是否能访问成员变量,默认是返回YES,如果返回YES,则依次会查找Person类里面是否有_key,_isKey,key,isKey成员变量,如果有则赋值给它。如果都没有则抛出setValue:forUndefinedKey:异常。如果accessInstanceVariablesDirectly方法返回NO,也抛出setValue:forUndefinedKey:异常。

    setValue:forKey流程图

    valueForKey:是如何取值的

    当我们调用[person valueForKey:@"age"]方法时,跟上述方法类似,系统会依次做如下操作:

    按顺序依次查找Person类里面有没有getKey,key,isKey,_key,如果有则直接调用方法,如果都没有找到,那么也会调用accessInstanceVariablesDirectly方法,该方法是控制是否能访问成员变量,默认是返回YES,如果返回YES,则依次会查找Person类里面是否有_key,_isKey,key,isKey成员变量,如果有则直接去成员变量的值,如果都没有则抛出setValue:forUndefinedKey:异常。如果accessInstanceVariablesDirectly方法返回NO,也抛出setValue:forUndefinedKey:异常。

    valueForKey:流程图

    简单用代码来验证一下:

    @interface Person : NSObject
    {
        @public
        int age;
        int isAge;
    //    int _age;
    //    int _isAge;
    }
    @implementation Person
    
    @end
    @end
    Person *person = [[Person alloc] init];
    //        person->_age = 10;
    person->age = 11;
    person->isAge = 12;
    //        person->_isAge = 13;
    NSLog(@"%@",[person valueForKey:@"age"]);//11
    

    KVC与KVO

    如果我们用KVC给Person赋值,再给Person对象添加KVO,是否会触发KVO呢,答案是肯定的。
    由以上可知当我们使用KVC对age赋值时,系统会去查找set方法,从而调用set方法,相当于set赋值所以肯定会触发KVO。
    还有一种情况,如果没有找到set方法呢,比如下面这个Person类就没有set方法

    @interface Person : NSObject
    {
        @public
        int age;
    }
    @end
    
    @implementation Person
    
    @end
    
    @interface MJObserver : NSObject
    
    @end
    
    @implementation MJObserver
    
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
        NSLog(@"observeValueForKeyPath: %@",change);
    }
    
    @end
    
    MJObserver *observer = [[MJObserver alloc] init];
            
    Person *person = [[Person alloc] init];
            
    //添加KVO监听
    [person addObserver:observer forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
    //尝试用KVC修改属性
    [person setValue:@10 forKey:@"age"];
            
    [person removeObserver:observer forKeyPath:@"age"];
    打印:
    2020-05-08 18:06:15.771458+0800 XMGTestProject[91287:6076018] observeValueForKeyPath: {
        kind = 1;
        new = 10;
        old = 0;
    }
    

    从代码实践中可以看出也触发了KVO,从上一篇KVO的本质中知道,没有set方法但是触发了KVO说明是手动调用了willChangeValueForKey和didChangeValueForKey方法,从而触发了KVO。也就是说:

    [person setValue:@10 forKey:@"age"];
    相当于以下三行代码
    [person willChangeValueForKey:@"age"];
    person->age = 10;
     [person didChangeValueForKey:@"age"];
    

    面试题

    1、通过KVC修改属性会触发KVO么?
    答:会触发KVO监听,尽管没有setter方法,但是[person setValue:@10 forKey:@"age”];内部会调用willChangeValueForKey和didChangeValueForKey方法,相当于手动触发了KVO。
    2、KVC的赋值和取值过程是怎样的?原理是什么?
    答:看上面流程图

    相关文章

      网友评论

        本文标题:小码哥底层原理笔记:KVC的原理

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