KVC/KVO 进阶(一) 底层原理

导语

上篇文章介绍了一些KVC/KVO的简单用法,本篇介绍一下KVC的搜索查找方式和KVO的底层原理。


创建 person 和 car 类

person.h

@class Car;
@interface Persion : NSObject
{
    NSString *_adress;
}
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) Car *car;
@end

Car.h

@interface Car : NSObject
@property (nonatomic, strong) NSNumber *price;
@end

ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    Persion *persion = [[Persion alloc] init];
    [persion setValue:@"zyy1" forKey:@"name"];
    [persion setValue:@"zyy2" forKeyPath:@"name"];
    NSLog(@"persion.name===%@",persion.name);

    Car *car = [[Car alloc] init];
    persion.car = car;
    [persion setValue:@500 forKeyPath:@"car.price"];
    NSLog(@"car.price====%@",[persion valueForKeyPath:@"car.price"] );

    [persion setValue:@"北京大望路1" forKeyPath:@"adress"];
    [persion setValue:@"北京大望路2" forKeyPath:@"_adress"];
    NSLog(@"adress===%@", [persion valueForKey:@"adress"]);
    NSLog(@"_adress===%@",[persion valueForKey:@"_adress"]);
}

输出结果

2016-10-27 15:00:01.989 KVC[5749:227850] persion.name===zyy2
2016-10-27 15:00:01.989 KVC[5749:227850] car.price====500
2016-10-27 15:00:01.990 KVC[5749:227850] adress===北京大望路2
2016-10-27 15:00:01.990 KVC[5749:227850] _adress===北京大望路2

KVC的赋值方式

当调用[persion setValue:@”zyy1” forKey:@”name”]的代码时,底层的执行机制如下:

1、 程序优先调用setName方法,代码通过setter方法完成设置
2 、 如果没有找到setName方法,KVC机制会检查+ (BOOL)accessInstanceVariablesDirectly方法有没有返回YES,默认该方法会返回YES,如果你重写了该方法让其返回NO的话,那么在这一步KVC会执行setValue:forUNdefinedKey:方法,不过一般开发者不会这么做。所以KVC机制会搜索该类里面有没有名为name的成员变量,无论该变量是在类接口部分定义,还是在类实现部分定义,也无论用了什么样的访问修饰符,只在存在以命名的变量,KVC都可以对该成员变量赋值。
3、如果该类即没有set:方法,也没有_成员变量,KVC机制会搜索_is的成员变量
4、和上面一样,如果该类即没有set:方法,也没有_和_is成员变量,KVC机制再会继续搜索和is的成员变量。再给它们赋值
5、如果上面列出的方法或者成员变量都不存在,系统将会执行该对象的setValue:forUNdefinedKey:方法,默认是抛出异常(大部分情况下,我们没有重载这个方法,程序是会直接carsh的)


如果想让这个类内部禁用KVC,那么重写+ (BOOL)accessInstanceVariablesDirectly方法让其返回NO即可,这样的话如果KVC没有找到set:属性名时,会直接用setValue:forUNdefinedKey:方法。


KVC的取值方式

当调用[persion valueForKey:@”adress”]的代码时,KVC对key的搜索方式不同于setValue:属性值 forKey:@”name“,其搜索方式如下:

1、首先按get<你的key>、<你的key>、is<你的key>的顺序查找getter方法,找到直接调用。如果是bool、int等内建值类型,会做NSNumber的转换。
2、上面的getter没有找到,查找countOf<你的key>、objectIn<你的key>AtIndex:、<你的key>AtIndexes格式的方法。 如果countOf<你的key>和另外两个方法中的一个找到,那么就会返回一个可以响应NSArray所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSArray消息方法,就会以countOf<你的key>、objectIn<你的key>AtIndex:、<你的key>AtIndexes这几个方法组合的形式调用。还有一个可选的get<你的key>:range:方法。
3、还没查到,那么查找countOf<你的key>、enumeratorOf<你的key>、memberOf<你的key>:格式的方法。如果这三个方法都找到,那么就返回一个可以响应NSSet所有方法的代理集合(collection proxy object)。发送给这个代理集合(collection proxy object)的NSSet消息方法,就会以countOf<你的key>、enumeratorOf<你的key>、memberOf<你的key>:组合的形式调用。
4、还是没查到,那么如果类方法accessInstanceVariablesDirectly返回YES,那么按_<你的key>,_is<你的key>,<你的key>,is<你的key>的顺序直接搜索成员名。
5、再没查到,调用valueForUndefinedKey:。


KVO底层原理

// 让self去观察persion对象的name属性 (self为观察者、监听者, persion为被观察者、被监听者)
    [persion addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil];
    persion.name = @"zyy3";
}


/**
 当被监听的对象的属性值发生改变时,观察者会调用该方法
 @param keyPath 监听的属性
 @param object  监听的对象
 @param change  新旧值
 @param context 额外参数
*/
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    NSLog(@"%@发生改变",keyPath);
    NSLog(@"change==%@",change);
}

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


涉及到了runtime,关于isa指针

1、当一个类(A)的属性被观察的时候,系统会通过runtime动态的创建一个A类的派生类(B)
2、B类继承于A类
3、将A类的isa指针指向B类
4、在B类中重写被观察的属性的setter方法
5、重写的setter方法会在调用原setter方法前后,通知观察对象值得改变


具体实现图如下

这里借鉴网上比较火的一张图

这里写图片描述

猜你喜欢

转载自blog.csdn.net/u014641631/article/details/52945701