1、 KVO是什么?
- KVO 全称
Key Value Observing
,是苹果提供的一套事件通知机制。允许对象监听另一个对象特定属性的改变,并在改变时接收到事件
。由于 KVO 的实现机制,属性变化还有通过kvc进行修改的,一般继承自 NSObject 的对象都默认支持 KVO。 - KVO 可以监听
单个属性
的变化,也可以监听集合对象
的变化。集合对象需要通过KVC 的 mutableArrayValueForKey:
等方法获得代理对象(例如数组会创建:创建一个NSKeyValueSlowMutableArray
中间对象),当代理对象的内部对象发生改变时,会回调 KVO 监听的方法。集合对象包含 NSArray 和 NSSet。
2、 KVO的基本使用
基本使用分为4步:
2.1 注册观察者
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew) context:NULL];
- NSKeyValueObservingOptionNew:在触发函数返回新值;
- NSKeyValueObservingOptionOld:在触发函数返回旧值;
2.2 被观察者发生变化
self.person.nickName = @"Henry";
[self.person setValue:@"Henry" forKey:@"nickName"];
[self setValue:@"Henry" forKeyPath:@"person.nickName"];
- 这3种方式都可以,尤其是监听集合类型时需要格外注意,需要使用KVC。
2.3 触发监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"%@",change);
}
输出:
- kind:表示监听方式
2.4 销毁
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"nickName"];
}
- 使用需要及时进行合法销毁;
3、KVO原理
3.1 isa-swizzling
NSLog(@"添加KVO之前-%@-%p", object_getClass(self.person),object_getClass(self.person));
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:NULL];
NSLog(@"添加KVO之后-%@-%p", object_getClass(self.person),object_getClass(self.person));
输出:
- 会发现在
addObserver
之后,类的Isa指向发生了变化
3.1.1 NSKVONotifying_XXX 中间派生类
猜测NSKVONotifying_LGPerson这个类是系统动态
进行添加,所以需要分析它的进行关系。获取LGPerson
的子类
#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls{
//获取所有类
int count = objc_getClassList(NULL, 0);
Class* classes = (Class*)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
for (int i = 0; i<count; i++) {
if (cls == class_getSuperclass(classes[i])) {
NSLog(@"classes = %@", classes[i]);
}
}
}
// 遍历类以及子类
[slef printClasses:[LGPerson class]];
[self.person addObserver:self forKeyPath:@"nickName" options:(NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld) context:NULL];
NSLog(@"绑定之后");
// 遍历类以及子类
[slef printClasses:[LGPerson class]];
输出:
-
LGStudent
是LGPerson
的一个子类; -
LGPerson
在绑定之后出现了一个新的子类NSKVONotifying_LGPerson
; - kvo第一步之后会将对象
self.person
的isa动态指向了NSKVONotifying_LGPerson
。这个类,这就是isa-swizzling
。
3.1.2 NSKVONotifying_XXX 类中的有什么
NSLog(@"绑定之后");
NSLog(@"~~~LGPerson~~~方法~~~");
// 遍历类的所有方法
[self printClassAllMethod:[LGPerson class]];
NSLog(@"~~~NSKVONotifying_LGPerson~~~方法~~~");
[self printClassAllMethod:objc_getClass("NSKVONotifying_LGPerson")];
NSLog(@"~~~LGPerson~~~属性~~~");
// 遍历类的所有属性
[self printClassAllIvar:[LGPerson class]];
NSLog(@"~~~NSKVONotifying_LGPerson~~~属性~~~");
[self printClassAllIvar:objc_getClass("NSKVONotifying_LGPerson")];
-
输出:
- NSKVONotifying_LGPerson有四个方法:
setNickName
,class
,dealloc
,_isKVOA
- NSKVONotifying_LGPerson中没有属性
- NSKVONotifying_LGPerson有四个方法:
-
上方使用到的两个
runtime
方法:
#pragma mark - 遍历方法
- (void)printClassAllMethod:(Class)cls{
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i<count; i++) {
Method method = methodList[i];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
#pragma mark - 遍历属性-ivar
- (void)printClassAllIvar:(Class)cls{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList(cls, &count);
for (int i = 0; i<count; i++) {
Ivar ivar = ivars[i];
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
NSLog(@"%@",ivarName);
}
free(ivars);
}
4、NSKVONotifying_XXX 中间派生类
4.1 NSKVONotifying_XXX 伪代码
// 这个核心方法作用是什么,在后面进行详解
- (void)setNickName:(NSString *)name{
...
}
// 我觉是一种混淆,为了隐藏NSKVONotifying_LGPerson的存在不被开发者发现
- (Class)class {
return [LGPerson class];
}
//销毁
- (void)dealloc {
// 收尾工作
}
// 这个方法应该是当做KVO的一个标记
- (BOOL)_isKVOA {
return YES;
}
4.2 setNickName
来到核心方法setNickName
之后,由于NSKVONotifying_LGPerson类中的setNickName
是系统生成的想要窥探一二就需要借助lldb
。
- 添加一个观察点
watchpoint set variable self->_person->_nickName
触发断点之后发现:
- 调用了set方法中的
NSKeyValueWillChange
; - 调用了
LGPerson
原生类中的set方法; - 调用了set方法中的
NSKeyValueDidChange
方法 - 最后由
NSKeyValueDidChange
调起了- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
注: 还可以将这两个方法增加为System breakpoint
,可以观察到更多信息,这里就不赘述了。
4.3 delloc
- (void)dealloc{
NSLog(@"销毁之前-%@-%p", object_getClass(self.person),object_getClass(self.person));
[self.person removeObserver:self forKeyPath:@"nickName"];
NSLog(@"销毁之后-%@-%p", object_getClass(self.person),object_getClass(self.person));
}
输出:
- 在销毁之后self.person的isa又被重新指向
NSKVONotifying_xxx
的父类;
4.3.1 delloc之后NSKVONotifying_XXX中间派生类怎么样了?
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"nickName"];
NSLog(@"销毁之后");
// 类的关系
[self printClasses:[LGPerson class]];
// 中间类的方法
[self printClassAllMethod:objc_getClass("NSKVONotifying_LGPerson")];
}
输出:
- 即使LGPerson的isa已经不指向派生类,可
派生类还是完整存在内存中
.
总结
在addObserver
之后:
- 系统动态创建了中间派生类
NSKVONotifying_xxx
1.1 在派生类中重写了set
,delloc
方法,并创建新方法class
,_isKVOA
; - 将
被观察的类
(LGPerson)isa指向新建的中间派生类NSKVONotifying_xxx
;
在被观察的者
发生变化:
- 调用了set方法中的
NSKeyValueWillChange
; - 调用了
LGPerson
原生类中的set方法; - 调用了set方法中的
NSKeyValueDidChange
方法; - 最后由
NSKeyValueDidChange
调起了回调方法
将改变信息送出;
在被观察的者
销毁时:
- 将
被观察的类
的isa重新指向NSKVONotifying_xxx
的父类 - 将
NSKVONotifying_xxx
保存到内存中,等待下次使用
网友评论