04 iOS底层原理 - KVC本质探究

2020-01-30  本文已影响0人  程序小胖

老规矩,还是先来两个面试题:

一,通过KVC修改属性会触发KVO吗?
二,KVC的赋值和取值过程是怎样的?原理是什么?

什么是KVC呢?

KVC的全称是Key-Value Coding,即“键值编码”,可以通过一个key来访问某个属性

废话不多说,直接上代码

一,准备代码

1. 创建Student类 继承自NSObject
// 声明Student
@interface Student : NSObject
@property (nonatomic, assign) int height;
@end

// 声明Person
@interface Person : NSObject
{
    @public
    int _age;
    int _isAge;
    int age;
    int isAge;
}
// 测试 setAge: 和 _setAge: 调用流程时,注调这句代码
@property (nonatomic, assign) int age;
@property (nonatomic, strong) Student *student;
@end

// 实现 
@implementation Student

@end

@implementation Person

//- (int)getAge {
//    return 18;
//}
//- (int)age {
//    return 28;
//}
//- (int)isAge {
//    return 38;
//}
//- (int)_age {
//    return 48;
//}

- (void)setAge:(int)age {
    _age = age;
   NSLog(@"setAge == %d", age);
}

- (void)_setAge:(int)age {
    NSLog(@"_setAge == %d", age);
}

/*
- (void)willChangeValueForKey:(NSString *)key {
    [super willChangeValueForKey:key];
    NSLog(@"willChangeValueForKey");
}

- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"didChangeValueForKey-begin");
    [super didChangeValueForKey:key];
    NSLog(@"didChangeValueForKey-end");
}

// 默认返回YES,表示如果没有找到setKey:/_setKey:方法的话,
// 会按照_key,_isKey,key,isKey的顺序搜索成员变量,设置成NO就不这样搜索
+ (BOOL)accessInstanceVariablesDirectly {
    return NO;
}*/
@end

二,都有哪些KVC的方法呢?

赋值:
setter
setValue:forKey
setValue:forKeyPath

取值:
getter
valueForKey:
valueForKeyPath:

注意:

赋值和取值没有对应关系,
但是用setValue:forKeyPath赋值了.路径的值,
只能用getter和valueForKeyPath:方法获取

直接上代码

// KVC简单测试
- (void)kvcTest1 {
    Person *person = [[Person alloc]init];
    person.age = 10;
    NSLog(@"%d", person.age);
    NSLog(@"%@", [person valueForKey:@"age"]);
    NSLog(@"%@", [person valueForKeyPath:@"age"]);
    
    [person setValue:[NSNumber numberWithInt:11] forKey:@"age"];
    [person setValue:@11 forKey:@"age"];
    NSLog(@"%d", person.age);
    NSLog(@"%@", [person valueForKey:@"age"]);
    NSLog(@"%@", [person valueForKeyPath:@"age"]);

    // forKeyPath 不仅可以修改当前类key的值,还可以修改其他类key的值
    // 用setValue:forKeyPath赋值了.路径的值,
    // 只能用getter和valueForKeyPath:方法获取
    person.student = [[Student alloc]init];
    [person setValue:@180 forKeyPath:@"student.height"];
    NSLog(@"%d", person.student.height);
    NSLog(@"%@", [person valueForKeyPath:@"student.height"]);
}

三,通过修改KVC的值可以触发KVO吗?

先看测试代码:

- (void)kvcTest2 {
    Person *person = [[Person alloc]init];
    
    // 添加KVO
    [person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
    
    // 通过KVC修改属性
    [person setValue:@18 forKey:@"age"];
  
    // 移除监听
    [person removeObserver:self forKeyPath:@"age"];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@-%@-%@", keyPath, object, change);
}

分两种情况:

1. setKey:和_setKey:方法都存在,或者至少存在一种

在Person类里面把这两个方法打开测试

- (void)setAge:(int)age {
    _age = age;
    NSLog(@"setAge == %d", age);
}
- (void)_setAge:(int)age {
    NSLog(@"_setAge == %d", age);
}

打印结果:

age-<Person: 0x600003028220>-{
          kind = 1;
          new = 18;
          old = 10;
}

通过打印结果,发现确实可以通过KVC修改属新触发KVO

2. 没有setKey:和_setKey:方法,只有成员变量,还会触发KVO吗?

在Person类里面把这两句注掉

//- (void)setAge:(int)age {
//    _age = age;
//    NSLog(@"setAge == %d", age);
//}
//
//- (void)_setAge:(int)age {
//    NSLog(@"_setAge == %d", age);
//}

会的,首先会触发accessInstanceVariablesDirectly方法,返回yes,
然后按照_key,_isKey,key,isKey的顺序搜索成员变量,并赋值。
此时的KVC大概实现流程是这样的:

  1. 调用willChangeValueForKey
  2. 修改_key的值
  3. 调用didChangeValueForKey,didChangeValueForKey 中实现了KVO的监听事件

三,KVC赋值和取值原理

先看两张图:

1. setValue:forKey:赋值原理
image.png

按照示意图,我这里解释下KVC取值的原理:
setValue:forKey:的内部实现,可以分以下几种情况:

1>. 有属性值key

不管实现不实现setKey:方法,默认都是实现了该方法的,因此_setKey:不管实现还是不实现,_setKey:方法都不会被调用,同样也都不会去访问_key,_isKey,key,isKey这些成员变量

2>. 没有属性值key

a> 如果 setKey:和_setKey: 方法都实现了,那么只会调用setKey:方法,但不会去访问_key,_isKey,key,isKey这些成员变量;
b> 如果 setKey:和_setKey: 只实现其中一个方法,那么就只调用实现的那个方法,但不会去访问_key,_isKey,key,isKey这些成员变量。

3>. 没有属性值key,setKey:和_setKey:方法也没实现

accessInstanceVariablesDirectly默认返回YES,表示如果没有找到setKey:/_setKey:方法的话,会按照_key,_isKey,key,isKey的顺序搜索成员变量。

4>. 其他情况

没有_key,_isKey,key,isKey成员变量,或者accessInstanceVariablesDirectly返回NO,那么,就直接抛出异常NSUnknownKeyException。

总结:通过1和2看出,其实和属性值key存不存在没关系,只要存在setKey:或_setKey:方法,就都不会去访问_key,_isKey,key,isKey这些成员变量

2. valueForKey:取值原理
image.png

从上图可以看出,基本的流程和赋值是一样的,需要注意两点:

  1. 第一个红框框住的方法,是四个方法依次是:
    getKey、key、isKey、_key
  2. 第二个红框,如果没有找到相关的方法,
    并且accessInstanceVariablesDirectly返回NO时,报的错误和赋值时的不一样。
    调用:valueForUndefinedKey:并抛出错误NSUnknowKeyExcention

四,现在看看文章开头的面试题

一,通过KVC修改属性会触发KVO吗?
会触发KVO,因为KVC在本质上是调用了
willChangeValueForKey:和didChangeValueForKey:的

二,KVC的赋值和取值过程是怎样的?原理是什么?
这个过程就不说了,上面两幅图记住比啥都好

结束语:非常感谢,MJ老师,和Hank老师,收获挺多

上一篇下一篇

猜你喜欢

热点阅读