Runtime系列11之-Runtime的应用(四)
KVO的底层原理
- 我们知道,KVO是监听一个对象的属性是否发生改变.那么他是怎么监听的呢?
-
测试代码如下:
//1.创建一个Person类 #import <Foundation/Foundation.h> @interface Person : NSObject { @public; int _age; } //自己已经声明了成员变量,那么这句话就只会生成setter和gettet方法了 @property (nonatomic, assign) int age; @end #import "Person.h" @implementation Person @end //2.调用 #import "ViewController.h" #import "Person.h" @interface ViewController () @property (nonatomic, strong) Person *person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Person *p = [[Person alloc] init]; p.age = 0; _person = p; NSLog(@"---监听前--"); //1.监听属性 [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; NSLog(@"--监听后--"); } //2.改变时监听方法 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{ NSLog(@"person的值改变了:%d",_person.age); } //改变属性值 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ //_person->_age = 10; _person.age = 10; } @end
- 从上面的测试代码,当在Viewcontroller的touchBegan方法中使用
_person.age = 10;
时,一点击就回触发observeValueForKeyPath...
方法;但是当使用_person->_age = 10;
时,点击时不会触发observeValueForKeyPath...
方法,因此得出如下结论:- KVO的本质就是监听一个对象有没有调用setter方法
- 从上面的测试代码,当在Viewcontroller的touchBegan方法中使用
- 那么他是如何监听setter方法的调用呢?
- 通过在
addObserver...
方法监听前和监听后打断点看看,Person对象向有什么变化,如下图: -
监听前
-
监听后:
- 通过在
- 因此,分析出
[p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
方法做了如下事:- 用运行时(runtime)自定义一个Person类的子类 NSKVONotifying_Person
- 重写NSKVONotifying_Person的setAge方法,在内部恢复父类(Person)的setAge方法
[super setAge:age];
- 拿到观察者,调用观察者的
-observeValueForKeyPath: ofObject: change: context:
方法 - 如何让外界(父类Person)调用自定义子类NSKVONotifying_Person的setter方法?
- 修改当前对象的isa指针,指向NSKVONotifying_Person即可
-
代码举例,模拟实现KVO,在这里就不动态创建一个类了,通过系统的方法创建一个Person子类,代码如下:
//1. 模拟系统的方法:- (void)addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context;给NSObject添加一个类似方法. #import <Foundation/Foundation.h> @interface NSObject (KVO) - (void)zh_addObserver:(NSObject *_Nullable)observer forKeyPath:(NSString *_Nullable)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; @end #import "NSObject+KVO.h" #import <objc/runtime.h> #import "ZHKVONotifying_Person.h" @implementation NSObject (KVO) - (void)zh_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { // 1.创建Person子类ZHKVONotifying_Person(用系统方法创建) // 2.重写父类的set方法 // 3.修改isa指针,就是把当前对象指向一个新类 object_setClass(self, [ZHKVONotifying_Person class]); // 4.给对象绑定观测者对象 objc_setAssociatedObject(self, @"observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end //2. 子类ZHKVONotifying_Person #import <Foundation/Foundation.h> #import "Person.h" @interface ZHKVONotifying_Person : Person @end #import "ZHKVONotifying_Person.h" #import <objc/runtime.h> @implementation ZHKVONotifying_Person //重写set方法 - (void)setAge:(int)age { [super setAge:age]; // 5.监听到,通知观察者 // 5.1 获取观察者 id observer = objc_getAssociatedObject(self, @"observer"); // 5.2 调用观察者的方法 [observer observeValueForKeyPath:@"age" ofObject:observer change:nil context:nil]; } @end //3. 调用 #import "ViewController.h" #import "Person.h" #import "NSObject+KVO.h" @interface ViewController () @property (nonatomic, strong) Person *person; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; Person *p = [[Person alloc] init]; p.age = 0; _person = p; NSLog(@"---监听前--"); //1.监听属性 [p zh_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; // [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; NSLog(@"--监听后--"); } //2.改变时监听方法 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context{ NSLog(@"person的值改变了:%d",_person.age); } //改变属性值 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ _person.age = 10; } @end