iOS 底层探索 文章汇总
目录
- 一、什么是KVO
- 二、KVO基本使用
- 三、KVO实现原理
- 四、总结
- 参考
一、什么是KVO
KVO
是基于KVC
的,全称是Key-Value-Observer
键值观察者。KVO
提供一种机制,指定一个被观察的对象(A类),当对象某个属性(A中的属性name
)发生更改时,对象会获得通知,并作出相应处理;【且不需要给被观察的对象添加任何额外代码,就能使用KVO
机制】。KVO
是Objective-C
对观察者设计模式的一种实现。
KVO
在MVC
设计架构下的项目很适合实现Mode
模型和View
视图之间的通讯。
例如:代码中,在模型类M创建属性数据,在控制器中创建观察者,一旦属性数据发生改变观察者就会收到通知,然后刷新相应的视图。
官方文档:Key-Value Observing Programming Guide
二、KVO基本使用
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.person = [NAPerson new];
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
self.person.name = @"differ";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
CJLog(@"%@",change[NSKeyValueChangeNewKey]);
}
- (void)dealloc {
[self.person removeObserver:self forKeyPath:@"name" context:NULL];
}
1、context的作用
当前观察者观察了多个对象,当这些对象中有同名的KeyPath
时可以用来区分是哪个context
下的KeyPath
。eg:
static void *PersonNameContext = &PersonNameContext;
static void *AnimalNameContext = &AnimalNameContext;
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:PersonNameContext];
[self.animal addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:AnimalNameContext];
2、是否有必要移除观察者
有必要。官方文档提示如果没移除观察者在某些情况下会出现NSRangeException
异常崩溃。
3、手动开启KVO
默认情况下KVO
都是自动开启的,但是我们可实现手动开关KVO
@implementation NAPerson
// 自动开关
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
return NO;
}
- (void)setName:(NSString *)name {
[self willChangeValueForKey:@"name"];
_name = name;
[self didChangeValueForKey:@"name"];
}
@end
4、集合类型的观察
[self.person addObserver:self forKeyPath:@"dataArray" options:NSKeyValueObservingOptionNew context:NULL];
//[self.person.dataArray addObject:@"1"];不起作用
[[self.person mutableArrayValueForKey:@"dataArray"] addObject:@"1"];
三、KVO实现原理
@interface NAPerson : NSObject {
@public
NSString *_nickName;
}
@end
// ViewController
[self.person addObserver:self forKeyPath:@"_nickName" options:NSKeyValueObservingOptionNew context:NULL];
self.person->_nickName = @"differ";
[self.person removeObserver:self forKeyPath:@"_nickName" context:NULL];
通过对成员变量_nickName
添加KVO
观察后可以发现:KVO
不会对成员变量进行观察,只对属性进行观察。由此可猜想KVO
和属性的setter
进行了关联。
通过官方文档可知,KVO
会产生一个中间类,将观察对象的isa
指向了这个中间类。断点调试如下:
KVO产生的中间类是分类还是派生子类?
修改代码遍历KVO
观察对象的类以及子类
- (void)viewDidLoad {
[super viewDidLoad];
[self printClasses:[NAPerson class]];
self.person = [NAPerson new];
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
[self printClasses:[NAPerson class]];
}
#pragma mark - 遍历类以及子类
- (void)printClasses:(Class)cls {
// 注册类的总数
int count = objc_getClassList(NULL, 0);
// 创建一个数组, 其中包含给定对象
NSMutableArray *mArray = [NSMutableArray arrayWithObject:cls];
// 获取所有已注册的类
Class* classes = (Class*)malloc(sizeof(Class)*count);
objc_getClassList(classes, count);
for (int i = 0; i
从控制台输出的情况可以看出KVO
生成的中间类是别被观察对象类的派生子类。
KVO产生的中间类是继承还是重写属性的setter方法
打印出NSKVONotifying_NAPerson
类中的方法
[self printClassAllMethod:objc_getClass("NSKVONotifying_NAPerson")];
#pragma mark - 遍历当前类的方法
- (void)printClassAllMethod:(Class)cls {
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i
由于NSKVONotifying_NAPerson
类中存在setName
方法,因此KVO子类
重写了观察属性的setter
方法。
对象的isa什么时候指回NAPerson
在移除观察者前后断点打印类名可知移除观察者后被观察对象的isa
指回了原类
尽管移除了观察者,但是KVO
产生的派生子类并不会移除,依然存在于内存中。因为当前界面销毁后通过上一个界面打印NAPerson
类以及子类(printClasses:
)依然会出现NSKVONotifying_NAPerson
。
KVO产生的派生子类重写的setter做了什么操作
添加断点:
修改name进入断点:
进入汇编中可以看到调用父类setName:
方法前后调用了NSKeyValueWillChange、NSKeyValueDidChange
四、总结
基本原理:
-
KVO
是基于Runtime
机制实现的 - 当某个类的对象属性第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的
setter
方法。派生类在被重写的setter
方法内实现真正的通知机制 - 如果原类为
Person
,那么生成的派生类名为NSKVONotifying_Person
- 每个类对象中都有一个
isa
指针指向当前类,当一个类对象的第一次被观察,那么系统就会偷偷将isa
指针指向动态生成的派生类,从而在给被监控属性赋值是执行的是派生类的setter
方法 - 键值观察通知依赖于
NSObject
的两个方法:willChangeValueForKey:
和didChangeValueForKey:
,在一个被观察属性发生改变之前,willChangeValueForKey:
一定会被调用,这就会记录旧的值。而当改变发生后,didChangeValueForKey:
会被调用,继而observeValueForKey:ofObject:change:context:
也会被调用
关于KVO中间类,有如下说明:
- 实例对象
isa
的指向在注册KVO
观察者之后,由原有类更改为指向中间类 - 中间类重写了观察属性的
setter
方法、class、dealloc、_isKVOA
方法 -
dealloc
方法中,移除KVO
观察者之后,实例对象isa
指向由中间类更改为原类 - 中间类从创建后,就一直存在内存中,不会被销毁
KVO为子类的观察者属性重写的setter方法的工作原理相当于:
- ( void)setName:( NSString *)name {
[self willChangeValueForKey: @"name"];
[super setValue:name forKey: @"name"]; //调用父类的存取方法
[self didChangeValueForKey: @"name"];
}
特点:
观察者观察的是属性,只有遵循
KVO
变更属性值的方式才会执行KVO
的回调方法,例如是否执行了setter
方法、或者是否使用了KVC
赋值。如果赋值没有通过
setter
方法或者KVC
,而是直接修改属性对应的成员变量,例如:仅调用_name = @"newName"
,这时是不会触发KVO
机制,更加不会调用回调方法的。所以使用
KVO
机制的前提是遵循KVO
的属性赋值方式来变更属性值。