用runtime仿照系统自定义KVO -- 深度解析KVO原理

从上一篇(想看上一篇的点击这里)我们已经清楚了KVO是如何实现的,我们先来复习下,KVO的实现步骤:

1.自定义一个NSKVONotifying_Studet的子类

2.重写setName方法,在里面通知观察者

3.修改isa指针,指向NSKVONotifying_Studet类,好让外界调用这个子类。

好了,接着上面的讲,首先我们要仿照系统定义一个添加观察者的方法,系统是

- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullablevoid *)context;这个方法,不防

我们也同样实现类似的方法,所以我定义了一个NSObject的分类 - NSObject+KVO。因为要和系统的方法区分,我在前面

添加了一个前缀why:

NSObject+KVO.h:

#import <Foundation/Foundation.h>

@interface NSObject (KVO)

/** 监听某个对象的属性 */
- (void)why_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;

@end
接着我们仿照系统定义一个运行过程中系统生成的一个 NSKVONotifying_Studet的子类,此时我取名为

WHYNSKVONotifying_Studet:

WHYNSKVONotifying_Studet.h:

#import "Student.h"

@interface WHYKVONotifying_Student : Student

@end
Student的类,便于看,我还是把代码贴出来吧

Student.h:

#import <Foundation/Foundation.h>

@interface Student : NSObject

@property (nonatomic, copy) NSString *name;

@end
好了,按照上面的步骤,我们就走到第二步了,此时我们要重新setName方法,并发出通知给观察者。

但是在WHYNSKVONotifying_Studet子类中,我们并没有观察者,所以需要把观察者保存到当前对象,

即到NSObject+KVO中拿到观察者。

当我们到NSObject+KVO中发现并没有那个属性保存着观察者,那怎么拿到呢?

这时我们就要用到runtime,动态保存观察者,来实现这一变态的做法:

//把观察者保存到当前对象中,因为此类没有属性保存观察者,所以需要动态保存观察者,就要用到runtime
    objc_setAssociatedObject(self, @"observe", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
保存了观察者后,就要在 WHY NSKVONotifying_Studet子类中获得观察者:

//获取观察者
    id observe = objc_getAssociatedObject(self, @"observe");
然后再调用观察者方法,实现监听:

//调用观察者方法
    [observe observeValueForKeyPath:@"name" ofObject:self change:nil context:nil];
写了这些上面的第二步终于大功告成了。我们再来实现关键的第三部:

修改isa指针,实现对子类的调用,即在NSObject+KVO内的setName方法中修改isa指针:

//修改对象的isa指针
    object_setClass(self, [WHYKVONotifying_Student class]);
然后在外面实现对自定义的方法的调用:

Student *student = [[Student alloc] init];
    self.student = student;
    
    [student why_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
上面是分部讲解的思路,看得有些乱,我再把各个类的代码整体贴出来下:

ViewController.m中的代码:

#import "ViewController.h"
#import "Student.h"
#import "NSObject+KVO.h"

@interface ViewController ()

@property (nonatomic, strong) Student *student;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Student *student = [[Student alloc] init];
    self.student = student;
    
    [student why_addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
}

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

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    static NSInteger i = 0;
    NSString *name = [NSString stringWithFormat:@"Ocean - %zd", i++];
    self.student.name = name;
}
@end
WHY NSKVONotifying_Studet.m中的代码:

#import "WHYKVONotifying_Student.h"
#import <objc/message.h>

@implementation WHYKVONotifying_Student

- (void)setName:(NSString *)name {
    [super setName:name];
    
    //通知观察者调用observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context方法
    //但是此时拿不到观察者,所以需要把观察者保存到当前对象,即到NSObject+KVO的方法中拿到观察者
    
    //获取观察者
    id observe = objc_getAssociatedObject(self, @"observe");
    //调用观察者方法
    [observe observeValueForKeyPath:@"name" ofObject:self change:nil context:nil];
}

@end
NSObject+KVO.m中的代码:

#import "NSObject+KVO.h"
#import "WHYKVONotifying_Student.h"

#import <objc/message.h>

@implementation NSObject (KVO)

/** 监听某个对象的属性 */
- (void)why_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context {
    
    //把观察者保存到当前对象中,因为此类没有属性保存观察者,所以需要动态保存观察者,就要用到runtime
    objc_setAssociatedObject(self, @"observe", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    //修改对象的isa指针
    object_setClass(self, [WHYKVONotifying_Student class]);
}

@end
运行结果,如下图:

同样实现了KVO的监听,好了,这个就是仿照KVO底层设计的方法。

源码在我的GitHub中,想要的点击这里












评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值