iOS-KVO 实现原理

xiaoxiao2021-02-28  152

KVO 属性观察者 是观察者模式的一种具体实现,主要通过观察对象属性值的变化,触发对应的方法

下面学习下 KVO 的具体实现 和 底层实现原理


原生观察者实现

1、为 狗 对象 d 添加一个观察者为 自己, 同时监听对象 d->age 年龄 属性的改变

Dog *d = [Dog new]; [p addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];

2、实现观察者监听方法,实时监听 d->age 年龄 属性的改变,并获取改变后的 new 值

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

3、在对象销毁前,移除狗对象的观察者

- (void)dealloc { [self.d removeObserver:self forKeyPath:@"age"]; }

系统 KVO 的底层实现原理

KVO 是基于 runtime 机制实现的首先是给 NSObject 添加一个分类 (Category), 添加两个方法: 方法一:为自己添加观察者(observer)方法二:属性变化后执行的动作(action)为这个类的对象添加观察者(方法一)和实现属性变化后的动作方法(方法二)当某个类的对象第一次被观察时(方法一),系统就会在运行时动态地创建该类的一个派生类在这个类中实现添加观察者方法(方法一),并将观察者对象和自己关联起来,并将指针指向生成的派生类在这个派生类中重写基类中被观察属性的 setter 方法,并获取对象的关联对象然后在 setter 方法中,执行 关联对象 的方法二,触发对象变化后的 action 动作,这样属性变化后的方法将会执行,达到监听的作用

具体代码实现

一、给 NSObject 添加一个分类 (Category), 添加两个方法,并空实现
@interface NSObject (KVO) - (void)xm_addObserver:( NSObject * _Nonnull )observer forKeyPath:(NSString * _Nonnull)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context; - (void)xm_observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context; @end @implementation NSObject (KVO) #pragma mark - 空实现 - (void)xm_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context { } - (void)xm_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { } @end
二、为某个类的对象添加观察者(方法一)和实现属性变化后的动作方法(方法二)
Dog *d = [Dog new]; [d xm_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil]; - (void)xm_observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context { NSLog(@"%@------%@", keyPath, change[@"new"]); }
三、当某个类的对象第一次被观察时(方法一),在运行时动态地创建该类的一个派生类
@interface KV0_Dog : Dog @end
四、在这个类中实现添加观察者方法(方法一),并将观察者对象和自己关联起来,并将指针指向生成的派生类
#import "Dog.h" #import <objc/message.h> @implementation Dog - (void)xm_addObserver:( NSObject * _Nonnull )observer forKeyPath:(NSString * _Nonnull)keyPath options:(NSKeyValueObservingOptions)options context:(nullable void *)context { // 修改对象的 ias 指针 object_setClass(self, NSClassFromString(@"KV0_Dog")); // 关联对象,将观察者对象和自己关联起来 objc_setAssociatedObject(self, "observer", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } @end
五、在这个派生类中重写基类中被观察属性的 setter 方法,并获取对象的关联对象,执行关联对象 的方法二,触发对象变化后的 action
#import "KV0_Dog.h" #import <objc/message.h> #import "NSObject+KVO.h" @implementation KV0_Dog - (void)setAge:(NSInteger)age { // 重写父类的属性 setter 方法 [super setAge:age]; // 获取自己(父类)的关联对象,观察者对象 id observer = objc_getAssociatedObject(self, "observer"); // 触发观察者对象的 observeValueForKeyPath 方法, 并传递参数 [observer xm_observeValueForKeyPath:@"age" ofObject:self change:@{@"kind": @(1), @"new": @(age)} context:nil]; } @end
六、改变 Dog 的 age 属性,然后 步骤二的属性观察方法将会被执行,同时属性改变的新值也传递过来了

打印结果

age------1 age------2 age------3 age------4 ...
转载请注明原文地址: https://www.6miu.com/read-23238.html

最新回复(0)