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
...