从上一篇(想看上一篇的点击这里)我们已经清楚了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中,想要的点击这里。