一,KVO (Key-Value Observing)
KVO
是Objective-C
对观察者设计模式的一种实现,它提供一种机制,指定一个被观察对象(如A类),当对象中的某个属性
发生变化的时候,对象就会接收到通知,并作出相应的处理。在MVC
设计架构下的项目,KVO
机制很适合实现mode
模型和view
视图之间的通讯。例如:代码中,在模型类A创建属性数据,在控制器中创建观察者,一旦属性数据发生改变就收到观察者收到通知,通过KVO再在控制器使用回调方法处理实现视图B的更新;
二,KVO底层实现原理
1 KVO是基于runtime机制实现的
2 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
3 如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
4 每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
5 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey: 和 didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
6 补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类
三,KVO原理验证过程
3.1 环境准备
- 1 我们创建一个项目,创建一个
Person
类,里边有一个属性name
@interface Person : NSObject
@property (copy, nonatomic) NSString *name;
@end
- 2 在控制器中初始化该类的实例对象
person
,进行相关KVO常用三部曲
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.person = [[Person alloc]init];
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.person.name = [NSString stringWithFormat:@"测试+%@",@"1"];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"改变&&&&change %@",change);
}
-(void)dealloc{
[self.person removeObserver:self forKeyPath:@"name"];
}
以上就是相关的环境搭建,相信只要接触iOS开发的同学都知道怎么使用KVO,接下来就让我进一步验证相关原理的实现过程。
3.2 原理验证
- 1 验证如何对一个类的属性进行setter方法的改变?
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
NSLog(@"改变&&&&change %@",change);
}
打印结果
2020-10-27 22:40:25.093093+0800 TheoryOfKVO[1154:48721] 改变&&&&change {
kind = 1;
new = "\U6d4b\U8bd5+1";
}
我们在以上内容打印的过程中能看到相关的打印内容,kind = 1
我们进入代码的定义查看可知
typedef NS_ENUM(NSUInteger, NSKeyValueChange) {
NSKeyValueChangeSetting = 1,
NSKeyValueChangeInsertion = 2,
NSKeyValueChangeRemoval = 3,
NSKeyValueChangeReplacement = 4,
};
NSKeyValueChangeSetting = 1,
也就是属性的setter方法,NSKeyValueChangeInsertion = 2,
是集合类型的添加方法,NSKeyValueChangeRemoval = 3,
是集合的移除操作,NSKeyValueChangeReplacement = 4,
是可变集合类型的元素替换。
- 2 如何验证派生类
NSKVONotifying_Person
的存在?
我们进行断点调试;在程序刚刚初始化self.person
的时候以及添加观察者的时候进行断点调试,看看类的信息变化,通过object_getClassName
输出当前类信息
过掉第一个断点,进入第二个断点,再次打印类的信息,
从而就验证了底层会生存一个派生类,继承自Person
- 3 如何验证通过
willChangeValueForKey
和didChangevlueForKey
来改变监听属性的值,从而达到修改的目的?
这一步其实很好验证的,我们在实现的类中重写+(BOOL)accessInstanceVariablesDirectly
使其返回值为NO
,这样如果我们不手动实现以上的两个改变属性值得方法,我们是打印不出任何值得改变的,然而我们手动实现这两个方法时,
-(void)setName:(NSString *)name
{
[self willChangeValueForKey:name];
_name = [name copy];
[self didChangeValueForKey:name];
}
+(BOOL)accessInstanceVariablesDirectly
{
return NO;
}
此时控制台打印的内容和我们监听的一抹一样。这就是通过willChangeValueForKey
和 didChangevlueForKey
改变属性值得过程;
- 4 如何验证通知结束后
isa
指向返回原类,派生类NSKVONotifying_Person
释放
我们在程序该控制器销毁的时候打印相关的结果,断点调试
(lldb) po object_getClassName(self.person)
"Person"
以上就是相关的验证过程;
四,总结
以上就是本人通过学习KVO得出的相关结论和验证,至于自定义KVO的实现,其中用到了很多Runtime的API,还不是完全掌握,后续如果自己有更深的立即将会继续补充完整,希望是一个不断学习和提升的过程。有不足之处请多多指教。