美文网首页
KVC 的原理分析

KVC 的原理分析

作者: Irene_球球 | 来源:发表于2019-07-14 22:57 被阅读0次

    定义

    KVC 是 Key Value Coding 的简称,键值对编码,遵循 NSKeyValueCoding 协议,可以像操作字典一样操作一个对象,通过 key 来直接取值和赋值的机制,而不是通过调用 setter、getter 方法访问。

    相关API

    由下图 NSKeyValueCoding.h 头文件中,我们可以看到一些相关的 API。

    1.png
    其中,以下四个是我们较为常用的 API
    - (void)setValue:(id)value forKeyPath:(NSString *)keyPath;
    - (void)setValue:(id)value forKey:(NSString *)key;
    - (id)valueForKeyPath:(NSString *)keyPath;
    - (id)valueForKey:(NSString *)key; 
    

    API 区别

    这里简单说一下 forKey 以及 forKeyPath 的区别。

    @interface Student : NSObject
    @property (nonatomic, assign) int age;
    @end
    
    @interface Teacher : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, strong) Student *student;
    @end
    

    如上述代码所示,一个 Teacher 类中,包含一个 name 的属性和一个 student的对象,Student 对象中包含 age 的属性,那么,如下代码所示可以看出,对于 Teacher 类中的 name 属性使用 KVC 方法,无论是使用 setValue: forKey: 或是 setValue: forKeyPath: 都是可以实现的。但是,如果是对 student 中的 age,则必须使用 setValue: forKeyPath:方法。
    所以,我们不难看出,key 是只能访问当前对象的属性,如果想要层层向下访问的话,就需要使用 keyPath

        self.teacher = [[Teacher alloc] init];
        self.teacher.name = @"老明";
        
        // 这里如果想要对 student 中的属性进行赋值,那么必须先对其进行实例化
        self.teacher.student = [[Student alloc] init];
        self.teacher.student.age = 10;
        [self.teacher setValue:@"老李" forKey:@"name"];
        NSLog(@"teacherName1 = %@",self.teacher.name);
        NSLog(@"studentAge1 = %d",self.teacher.student.age);
        
        [self.teacher setValue:@"老刘" forKeyPath:@"name"];
        [self.teacher setValue:@30 forKeyPath:@"student.age"];
        NSLog(@"teacherName2 = %@",self.teacher.name);
        NSLog(@"studentAge2 = %d",self.teacher.student.age);
    
        log打印出来结果如下:
        teacherName1 = 老李
        studentAge1 = 10
        teacherName2 = 老刘
        studentAge2 = 30
    

    是否触发 KVO

    对 teacher 的 name 属性执行监听,查看其回调方法observeValueForKeyPath:ofObject:change:context:是否执行

        // 1. 添加 KVO 监听
        [self.teacher addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
        // 2. 通过 KVC 修改 name 属性值
        [self.teacher setValue:@"teacherNew" forKey:@"name"];
        // 3. 移除监听
        [self.teacher removeObserver:self forKeyPath:@"name"];
    
    log 打印结果如下:
     object: <Teacher: 0x600001f1ddc0>
    keyPath: name
    change: {
        kind = 1;
        new = teacherNew;
        old = "\U8001\U5218";
    }
    

    从上述打印结果不难看出,KVC 修改属性会触发 KVO。

    setValue:forKey: 原理

    1. setValue:forKey: 方法在调用时,首先会去调用 setKey: 方法,如果找不到方法,则会查找调用 _setKey: 的方法,如果找到方法,那么直接传递参数调用方法,如果两个方法均找不到,那么调用 accessInstanceVariablesDirectly
      其中 accessInstanceVariablesDirectly(是否能直接访问成员变量) 方法的默认返回值是YES。
    2. accessInstanceVariablesDirectly 返回 NO,则抛出异常。
    3. accessInstanceVariablesDirectly 返回 YES,则按顺序 _key、_isKey、key、isKey 依次往后的顺序去查找成员变量,如果找到成员变量,则直接赋值,找不到则抛出异常。

    具体方法调用步骤,可参照下图所示流程:

    setValue:forKey:
    如果所示步骤,可通过依次代码设置 setKey 以及 _setKey: 方法来进行验证。

    valueForKey: 原理

    通过 setValue:forKey: 方法,不难得出 valueForKey: 的执行顺序。

    1. 按顺序 getKey、key、 isKey、_key 依次往后的顺序去调用取值,如果找到方法,则直接调用方法。
    2. accessInstanceVariablesDirectly 返回 NO,则抛出异常。
    3. accessInstanceVariablesDirectly 返回 YES,则按顺序 _key、_isKey、key、isKey 依次往后的顺序去查找成员变量,如果找到成员变量,则直接取值,找不到则抛出异常。
      valueForKey:

    相关文章

      网友评论

          本文标题:KVC 的原理分析

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