细说OC中的KVO

KVO这个特异功能相信很多人都应该熟知, 就算工作时没有用到, 那么面试的时候肯定被面试官提到过, 虽然算不上黑魔法, 但是了解一下实现原理, 对我们还是有很大帮助的, 下面笔者将一步一步深挖KVO的实现原理!

创建一个Person类, 添加一个属性name, 当然你可以添加任何你想要的属性, 这里笔者就用name来做演示使用

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@end

NS_ASSUME_NONNULL_END

//一下为实现文件, 代码内容暂时不要纠结, 接下来会细说

 //控制器中使用KVO
#import "ViewController.h"
#import "Person.h"
#import <objc/runtime.h>

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self test];
}

- (void)test {
    Person *person = [[Person alloc] init];
    person.name = @"lisi";
    [person addObserver:self forKeyPath:@"name"     options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:NULL];
    person.name = @"zhangsan";
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    if ([keyPath isEqualToString:@"name"]) {
          NSLog(@"%@", change);
    }

}


以上代码运行结果如下

2018-09-05 09:06:04.437498+0800 KVODemo[29221:23851046] {
    kind = 1;
    new = zhangsan;
    old = lisi;
}

因为是在赋值的时候才能获取到通知, 所以首先应该想到, setter方法, 于是笔者给Setter方法加入log

运行结果如下

2018-09-05 09:19:01.981135+0800 KVODemo[29406:23866065] lisi
2018-09-05 09:19:01.981448+0800 KVODemo[29406:23866065] zhangsan
2018-09-05 09:19:01.981610+0800 KVODemo[29406:23866065] {
    kind = 1;
    new = zhangsan;
    old = lisi;
}

我们看到在第二次赋值时, 发出了通知, 那么是否是因为Setter方法不一样而导致的呢, 于是笔者加入了打印Setter方法地址的代码如下

- (void)test {
    Person *person = [[Person alloc] init];
    person.name = @"lisi";

    SEL sel = @selector(setName:);

    IMP imp = [person methodForSelector:sel];

    NSLog(@"添加KVO之前的setter地址%p", imp);


    [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:NULL];
    person.name = @"zhangsan";
    sel = @selector(setName:);
    imp = [person methodForSelector:sel];
    NSLog(@"添加KVO之后的setter地址%p", imp);

}

运行结果如下,

2018-09-05 09:26:02.811444+0800 KVODemo[29515:23878046] lisi
2018-09-05 09:26:02.811580+0800 KVODemo[29515:23878046] 添加KVO之前的setter地址0x1036dc570
2018-09-05 09:26:02.811835+0800 KVODemo[29515:23878046] zhangsan
2018-09-05 09:26:02.811994+0800 KVODemo[29515:23878046] {
    kind = 1;
    new = zhangsan;
    old = lisi;
}
2018-09-05 09:26:02.812079+0800 KVODemo[29515:23878046] 添加KVO之后的setter地址0x103a359fa

观察结果会发现两次的地址是不一样的, 那么可以确定第二次调用的setName: 方法肯定是变了, 但是明明是同一个对象的同一个方法, 为什么不一样呢, 所以我们要看看两个不同的时间节点两个对象的真是类型, 然后加入以下代码

- (void)test {
    Person *person = [[Person alloc] init];
    person.name = @"lisi";

    SEL sel = @selector(setName:);

    IMP imp = [person methodForSelector:sel];

    Class clazz = object_getClass(person);
    Class supClazz = class_getSuperclass(clazz);


    NSLog(@"添加KVO之前的类: %@, 父类:%@", clazz, supClazz);
    NSLog(@"添加KVO之前的setter地址%p", imp);


    [person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld context:NULL];
    person.name = @"zhangsan";

    clazz =  object_getClass(person);
    supClazz = class_getSuperclass(clazz);

    NSLog(@"添加KVO之后的类名: %@, 父类:%@", clazz, supClazz);


    sel = @selector(setName:);
    imp = [person methodForSelector:sel];
    NSLog(@"添加KVO之后的setter地址%p", imp);


}

通过运行时方法 object_getClass() 获取运行时当前对象的类型, 并通过class_getSuperclass() 获取父类, 在加入观察者之前, 和之后分别调用以下, 得到以下结果

2018-09-05 10:49:34.127586+0800 KVODemo[30673:23964294] lisi
2018-09-05 10:49:34.127717+0800 KVODemo[30673:23964294] 添加KVO之前的类: Person, 父类:NSObject
2018-09-05 10:49:34.127802+0800 KVODemo[30673:23964294] 添加KVO之前的setter地址0x105bf7500
2018-09-05 10:49:34.128079+0800 KVODemo[30673:23964294] zhangsan
2018-09-05 10:49:34.128283+0800 KVODemo[30673:23964294] {
    kind = 1;
    new = zhangsan;
    old = lisi;
}
2018-09-05 10:49:34.128388+0800 KVODemo[30673:23964294] 添加KVO之后的类名: NSKVONotifying_Person, 父类:Person
2018-09-05 10:49:34.128468+0800 KVODemo[30673:23964294] 添加KVO之后的setter地址0x105f509fa

从运行结果中可以可以看到, 加入观察者之后, person的实际类型已经变了, 此时的person实际类型为NSKVONotifying_Person, 继承自Person, 到此不难得出一个结论, 即苹果在代码运行时创建了继承自Person类的NSKVONotifying_Person类用来实现对Person类属性的的监测的, 那么他是如何实现的呢? 接下来笔者将继续挖掘.

NSObject有两个方法 分别是 - (void)willChangeValueForKey:(NSString )key; 和- (void)didChangeValueForKey:(NSString )key ;

当有值发生变化的时候系统会调用这个两个方法

接下来我们重写Person类的这两个方法, 来验证KVO的调用过程, 代码如下

@implementation Person

- (void)setName:(NSString *)name {
    _name = name;
    NSLog(@"%@", name);
}

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

- (void)didChangeValueForKey:(NSString *)key {
    NSLog(@"didChange前");
    [super didChangeValueForKey:key];
    NSLog(@"didChange后");
}

@end

运行结果如下

2018-09-05 13:22:24.169036+0800 KVODemo[32769:24109458] lisi
2018-09-05 13:22:24.169173+0800 KVODemo[32769:24109458] 添加KVO之前的类: Person, 父类:NSObject
2018-09-05 13:22:24.169239+0800 KVODemo[32769:24109458] 添加KVO之前的setter地址0x10e704330
2018-09-05 13:22:24.169510+0800 KVODemo[32769:24109458] willChange前
2018-09-05 13:22:24.169599+0800 KVODemo[32769:24109458] willChange后
2018-09-05 13:22:24.169655+0800 KVODemo[32769:24109458] zhangsan
2018-09-05 13:22:24.169726+0800 KVODemo[32769:24109458] didChange前
2018-09-05 13:22:24.169878+0800 KVODemo[32769:24109458] {
    kind = 1;
    new = zhangsan;
    old = lisi;
}
2018-09-05 13:22:24.169989+0800 KVODemo[32769:24109458] didChange后
2018-09-05 13:22:24.170089+0800 KVODemo[32769:24109458] 添加KVO之后的类名: NSKVONotifying_Person, 父类:Person
2018-09-05 13:22:24.170168+0800 KVODemo[32769:24109458] 添加KVO之后的setter地址0x10ea5d9fa

当name的值修改为zhangsan时, 系统生成一个子类, NSKVONotifying_Person, 并且重写了setName:, didChangeValueForKey: 以及class方法, 这样在调用setName的时候, 去调用didChangeValueForKey并添加观察者的通知, 而我们 调用[person class]时返回的是Person, 说明在子类中也重写了class方法并返回Person.

以上是对KVO的简单分析, 如有错误之处请各位读者不吝指教!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值