OC目标 – KVO
俗称“键值监听” ,用来监听某个特点值的改变
1. KVO基本运用
1.1 简略的KVO
- 首要咱们新建一个iOS的App项目
- 新建
ZSXPerson
类
@interface ZSXPerson : NSObject
@property (nonatomic, assign) int age;
@end
@implementation ZSXPerson
@end
- viewController 中创立
ZSXPerson
特点并初始化,然后增加KVO监听实例目标的age
改变,接着通过 touchesBegan 点击屏幕的时分修正age
值,来触发 KVO
#import "ViewController.h"
#import "ZSXPerson.h"
@interface ViewController ()
@property (nonatomic, strong) ZSXPerson *person1;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_person1 = [[ZSXPerson alloc] init];
[_person1 addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"点击了屏幕");
_person1.age = 20;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
NSLog(@"监听到 %@的%@的值发生改变:%@", object, keyPath, change);
}
- (void)dealloc {
[_person1 removeObserver:self forKeyPath:@"age"];
}
@end
- 运行项目,点击屏幕,查看控制台输出
- 现在咱们已经运用 KVO 正常监听 person 的 age 值改变
2. 调查给目标增加kvo监听和没有增加kvo监听的差异
2.1 增加person2目标
咱们再增加一个person2特点并初始化,同样在点击屏幕的时分,给person2.age也赋值
此时点击屏幕,控制台的确只输出 person1 目标的监听改变
2.2 思考
- 咱们对person1和person2仅仅做了同样了age赋值操作,kvo是怎样做到增加了监听的目标才触发observeValue呢?
3. KVO底层完成探求
3.1 增加KVO前后,setAge办法发生了什么改变?
在增加KVO前后,别离打印一下办法完成的地址 发现person1 的 setAge 办法在增加完 KVO 监听后改变了,person2 是没有改变的
3.2 增加KVO前后,setAge究竟走了什么办法
咱们打印一下办法
- 直接打印办法地址看不到具体办法名,运用(IMP)强转一下
能够调查到
- 未增加KVO监听的 setAge 办法是
-[ZSXPerson setAge:]
- 增加KVO监听后 setAge 办法是
Foundation
_NSSetIntValueAndNotify`
3.2.1 其他数据类型
如果咱们还有一个double类型的特点
setHeight办法终究会是_NSSetDoubleValueAndNotify
3.3 查看isa
前面学过isa的指向,咱们知道目标办法
是寄存类目标
中的,既然两个目标的setAge目标办法不一样,那是不是他们isa指向的类目标也不一样呢
3.3.1 控制台打印
的确他们isa指向的类目标不是同一个,_person1
在这边没有显现类目标称号,_person2
能够看出来是ZSXPerson
3.4 运用runtime打印类目标
3.4.1通过runtime打印_person1的类目标:NSKVONotifying_ZSXPerson
3.4.2 打印_person1的类目标的superclass
一起还发现,NSKVONotifying_ZSXPerson
的superclass就是ZSXPerson
3.5 结论
增加KVO后
- 利用RuntimeAPI动态生成一个子类,而且让instance目标的isa指向这个全新的子类
NSKVONotifying_ClassName
- 当修正instance目标的特点时,会调用Foundation的
_NSSetXXXValueAndNotify
函数
3.6 _NSSetXXXValueAndNotify办法做了什么
_NSSetXXXValueAndNotify
是Foundation
结构的东西,由于无法拿到Foundation
的源码,可是能够通过一些逆向的手法,得到_NSSetXXXValueAndNotify
办法实际执行的伪代码
3.6.1 _NSSetXXXValueAndNotify
调用setAge
办法时,_NSSetXXXValueAndNotify
办法里边做的内容能够认为是这样:
- [self willChangeValueForKey:@”age”];
- [super setAge:age];
- [self didChangeValueForKey:@”age”];
- didChangeValueForKey 办法会调用监听器的(observeValueForKeyPath:ofObject:change:context:)办法
3.7 NSKVONotifying_ClassName
类目标里边还有什么办法
咱们遍历打印一下NSKVONotifying_ClassName
里边,看看它还有什么办法
- (void)printMethodNameOfClass:(Class)cls {
unsigned int count;
// 取得办法数组
Method *methodList = class_copyMethodList(cls, &count);
// 存储办法名
NSMutableString *methodNames = [NSMutableString string];
// 遍历所有办法
for (int i = 0; i < count; i++) {
// 取得办法
Method method = methodList[i];
// 取得办法名
NSString *methodName = NSStringFromSelector(method_getName(method));
// 拼接办法明
[methodNames appendFormat:@"%@", methodName];
[methodNames appendFormat:@", "];
}
// 开释
free(methodList);
// 打印办法名
NSLog(@"%@ %@", cls, methodNames);
}
点击屏幕的时分,咱们调用一下打印
3.7.1 setAge:
会调用Foundation
_NSSetIntValueAndNotify`
3.7.2 class:
你会发现,目标增加KVO监听后,isa只想了一个新的子类NSKVONotifying_ZSXPerson
,可是咱们在运用[person1 class]获取类目标的时分,回来的依然是ZSXPerson
这是由于:
苹果底层设计时,为了屏蔽内部完成,让开发者运用过程中,不会忽然看到一个反常的东西,避免想入非非
咱们能够认为NSKVONotifying_ZSXPerson
类重写了 class 办法如下
- (Class)class {
return [ZSXPerson class];
}
3.7.2 dealloc:
能够认为里边就是做了一些跟KVO开释有关的收尾操作
- (void)dealloc { // 收尾作业 }
3.7.2 _isKVOA:
回来是否是KVO的相关类
- (BOOL)_isKVOA {
return YES;
}
4. 总结
4.1 未运用KVO监听的目标
4.2 运用了KVO监听的目标
4.3 增加KVO后
- 利用RuntimeAPI动态生成一个子类,而且让instance目标的isa指向这个全新的子类
NSKVONotifying_ClassName
- 当修正instance目标的特点时,会调用Foundation的
_NSSetXXXValueAndNotify
函数 -
- willChangeValueForKey:
-
- 父类原来的setter
-
- didChangeValueForKey: 内部会触发监听器(Oberser)的监听办法( observeValueForKeyPath:ofObject:change:context:)
扩展
手动触发KVO
- 碰过这样一道面试题:运用下划线直接拜访成员变量的方式给变量赋值,会不会触发KVO监听?
答案是:不会。由于KVO重写的是set办法(setAge:)。直接给成员变量赋值不会走set办法,因此也不会触发KVO监听
- 然后又会问,那能不能手动触发KVO监听?
手动触发
只要手动调用实例目标的willChangeValueForKey:
和didChangeValueForKey
办法,就能触发
注意
需求别离调用willChangeValueForKey:
和didChangeValueForKey
办法才会触发KVO
- 内部完成在执行
didChangeValueForKey
办法的时分,会判别前面是否执行了willChangeValueForKey:
办法,前面有调用过才会触发KVO 监听
@oubijiexi