iOS KVO实现原理和FBKVOController的使用
iOS KVO实现原理和FBKVOController的使用
我们通常需要监听一个对象的某个属性值的变化,来动态的修改UI或者展示;
这时候KVO就排上了用场,KVO是苹果专门提供的用于监听某个对象的属性变化的方法;
例如:
要监听一个person对象的属性age值的变化,实现步骤如下;
1.系统KVO的使用
1、 给对象添加一个observer:
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[YYPerson alloc]init];
[self.person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
}
2、 实现observer回调方法:
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
if ([keyPath isEqualToString:@"age"]) {
NSLog(@"age = %d",[[change objectForKey:@"new"] intValue]);
}
}
3、 在dealloc方法中移除观察者:
- (void)dealloc{
[self.person1 removeObserver:self forKeyPath:@"age"];
}
2.KVO实现原理
要弄清楚KVO的实现原理,我们就得知道,在添加观察者和没添加观察者之间有什么区别呢?
如下图,一个添加了observer的person1和没有添加observer的person2,打印其isa指针,区别如下:
person1和person2的区别:
-
person1的 isa指针 指向: NSKVONotifying_YYPerson
-
person2的 isa指针 指向: YYPerson
2.1 未使用KVO监听的对象:
- 直接调用父类的setAge方法,改变成员变量_age的值;
2.2 添加了KVO监听的对像:
会通过runtime动态的生成一个 NSKVONotifying_YYPerson 的中间类;
-
然后实例对象person1的 isa指针 指向这个新生成的类;
-
NSKVONotifying_YYPerson 是YYPerson的子类,所以它的superclass指针指向YYPerson;
伪代码 模拟 NSKVONotifying_YYPerson 内部实现:
// .h
#import "YYPerson.h"
@interface NSKVONotifying_YYPerson : YYPerson
@end
//.m
@implementation NSKVONotifying_YYPerson
- (void)setAge:(int)age{
_NSSetIntValueAndNotify();
}
//_NSSetIntValueAndNotify() 方法为Foundation框架中的方法,此处为伪代码,模拟实现
void _NSSetIntValueAndNotify(){
[self willChangeValueForKey:@"age"];
//真正的去改变父类中的age值
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key{
//通知监听器, XXX属性值改变
[observer observeValueForKeyPath:@"age" ofObject:self change:value context:nil];
}
@end
-
person1.age调用流程:
-
通过person1的 isa指针 找到 NSKVONotifying_YYPerson中的setAge方法并调用
-
NSKVONotifying_YYPerson 中的setAge方法,会去调用Foundation框架中的方法_NSSetIntValueAndNotify();
-
_NSSetIntValueAndNotify()方法会依次调用如下方法:
-
调用willChangeValueForKey();
-
[super setAge:age]; 调用YYPerson中的setAge方法,真正改变age属性的值;
-
didChangeValueForKey() 通知监听器,age的值变化了,回调观察者中实现的回调方法;
-
-
重写YYPerson中的方法willChangeValueForKey和didChangeValueForKey验证调用流程:
2.3 NSKVONotifying_YYPerson中其它被重写的方法
1、 通过[self.person1 class]和runtime获取类方法object__getClass(self.person1)对比,可以发现中间类NSKVONotifying_YYPerson 重写了class方法,因为苹果不希望开发者知道这个类的存在,所以重写这个方法
在中间类中,NSKVONotifying_YYPerson还重写了其他哪些方法,可以使用以下打印method列表方法打印一下:
- (void)printMethodList:(Class )cls{
unsigned int count;
//获得类的方法数组
Method *methodList = class_copyMethodList(cls, &count);
//遍历所有的方法
NSMutableString *methodString = [NSMutableString string];
for (int i = 0; i < count; i ++) {
Method method = methodList[i];
NSString *mstring = NSStringFromSelector(method_getName(method));
//拼接方法名
[methodString appendString:mstring];
[methodString appendString:@","];
}
free(methodList); //methodList是通过C语言copy得到的对象,需要释放
NSLog(@"方法列表:%@",methodString);
}
分别传入person1和person2的类对象,打印结果:
这3个方法简单实现如下,其中class方法直接返回的是[YYPerson class],这样可以不让外界知道这个类的存在:
- (Class)class{
return [YYPerson class];
}
- (void)dealloc{
//在移除观察者的时候做收尾工作
}
- (BOOL)isKVOA{
return YES; //是不是KVO
}
2.4 验证
2、 验证 NSKVONotifying_YYPerson 的存在
3、 验证 NSKVONotifying_YYPerson 中的setAge调用了Foundation中的_NSSetIntValueAndNotify()方法
4、 _NSSetIntValueAndNotify()方法和属性的类型是int,double,char是对应的:
3.FBKVOController 的使用
1、 实例化一个controller对象,并添加监听:
//初始化后有一个是否强引用观察者参数retainObserved:NO可以避免循环引用问题
self.fbVC = [[FBKVOController alloc]initWithObserver:self retainObserved:NO];
[self.fbVC observe:self.person1 keyPath:@"age" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSLog(@"age = %d",[[change objectForKey:@"new"] intValue]);
}];
2、取消监听 (可以不取消)
注意: FBKVOController的一个特点就是:不移除observer,不会崩溃 所以这一步可以省略
- (void)dealloc{
[sekf.fbVC unobserve:self.person1];
//[self.fbVC unobserveAll]; //或者可以取消全部的监听
NSLog(@"%s",__func__);
}
3.1 系统KVO和FBKVOController优缺点
1.系统KVO的问题
- 当观察者被销毁之前,需要手动移除观察者,否则会出现程序异常(向已经销毁的对象发送消息);
- 可能会对同一个被监听的属性多次添加监听,这样我们会接收到多次监听的回调结果;
- 当观察者对多个对象的不同属性进行监听,处理监听结果时,需要在监听回调的方法中,作出大量的判断;
- 当对同一个被监听的属性进行两次removeObserver时,会导致程序crash。这种情况通常出现在父类中有一个KVO,在父类的dealloc中remove一次,而在子类中再次remove。
2、FBKVOController的优点
- 可以同时对一个对象的多个属性进行监听,写法简洁;
- 通知不会向已释放的观察者发送消息;
- 增加了block和自定义操作对NSKeyValueObserving回调的处理支持;
- 不需要在dealloc 方法中手动移除观察者,而且移除观察者不会抛出异常,当FBKVOController对象被释放时, 观察者被隐式移除;
例如:如下面代码,添加一个监听属性的方法有单独的block实现,不需要和系统的KVO那样在同一个方法里面去判断,解耦,看起来也简洁
[self.fbVC observe:self.person1 keyPath:@"age" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSLog(@"age = %d",[[change objectForKey:@"new"] intValue]);
}];
[self.fbVC observe:self.person2 keyPath:@"name" options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSKeyValueChangeKey,id> * _Nonnull change) {
NSLog(@"age = %@",[[change objectForKey:@"new"] intValue]);
}];
关于FBKVOController实现原理,可参考文章链接
上一篇: KVC详解
下一篇: 搭建frida+木木模拟器运行环境