iOS KVO的使用和原理实现
文章目录
一、KVC(键值编码 key-value-coding)
1、概念
2、简单使用
1> 注册观察者,添加观察对象属性
2> 实现回调方法
3> 移除观察者
4> 测试触发kvo
3、注意细节
二、KVO(键值监听 key-value-observer)
1、概念
2、如何使用
3、使用注意细节
三、KVO实现原理
1、原理总纲
2、验证原理
3、自己手动实现
一、KVC(键值编码)
1、概念 : 指在iOS的开发中,可以允许开发者通过Key名直接访问对象的属性,或者给对象的属性赋值。而不需要调用明确的存取方法。这样就可以在运行时动态在访问和修改对象的属性。
2、简单实用
定义一个person类,并在.h(.m)文件声明 name (NSString)属性
// 1、 创建对象
self.jack = [[Person alloc]init];
// 2、赋值 和取值
[jack setValue:@"jack" forKey:@"name"];
NSLog(@"%@",[jack valueForKey:@"name"]);
// 3、或者以下面的方式赋值和取值
[self.jack setValue:@"tom" forKeyPath:@"name"];
NSLog(@"%@",[self.jack valueForKeyPath:@"_name"]);
// 4、 如何访问的key不存在,发生异常。如果捕获这个异常呢?
// 重写 setValue: forUndefinedKey: 重写后程序能避免crash
- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{
NSLog(@" %@---%@",value,key);
}
3、注意细节
1> key的值必须正确,如果拼写错误,会出现异常;
2> valueForKey\ valueForKeyPath 方法根据key的值读取对象的属性,
setValue:forKey:\ forKeyPath: 是根据key的值来写对象的属性;
3> 推荐使用 valueForKeyPath \ setValue:forKeyPth;
4> 当key的值是没有定义的,valueForUndefinedKey:这个方法会被调用,如果重写了这个方法,就可以获取错误的key值。
5> [jack setValuesForKeysWithDictionary: ] 传入一个字典,如果key捕获不存在的时候,可调用forUndefinedKey,可用于模型公用
6> [self.jack setValue:@"tom" forKeyPath:@"dog.name"];
表示:Person类面有成员变量dog,这个dog是Dog类是实例,具有name属性。
二、KVO(键值监听)
1、概念 : 当指定的对象的属性被修改后,则对象就会接受到通知。简单的说就是每次指定的被观察的对象的属性被修改后,KVO就会自动通知相应的观察者了。
2、如何使用
1> 注册观察者,并添加观察对象属性
// contex能确保是监听是当前类的属性(有可能父类和子类监听同一属性的情况)
static NSString const *PrivateKVOContext;
[self.jack addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:&
PrivateKVOContext];
2> 实现回调方法
- (void)observeValueForKeyPath:(nullable NSString *)keyPath ofObject:(nullable id)object change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change context:(nullable void *)context
{
NSLog(@"2 ->%@", object_getClass(self.jack));
if ([keyPath isEqualToString:@"name"] && object == self.jack && context == &PrivateKVOContext) {
id oldName = [change objectForKey:NSKeyValueChangeOldKey];
NSLog(@"oldName----------%@",oldName);
id newName = [change objectForKey:NSKeyValueChangeNewKey];
NSLog(@"newName-----------%@",newName);
}else{
/*
若当前类无法捕捉到这个KVO,那很有可能是在他的superClass,
或者super-superClass...中
*/
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
3> 移除观察者
[self.jack removeObserver:self forKeyPath:@"name" context:&PrivateKVOContext];
4> 测试触发kvo
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 1、set 方法触发 kvo
//jack.name = @"你好";
// 2、KVC 触发kvo
// [jack setValue:@"jack" forKey:@"name"];
[self.jack setValue:@"jack" forKeyPath:@"name"];
}
3、细节注意
1> key 的值必须正确,如果拼写出现错误,会出现异常
2> 父类和子类同时存在KVO时(监听同一个对象的同一个属性),很容易出现对同一个keyPath进行两次removeObserver操作,从而导致程序crash。 要避免这个问题,就需要区分出KVO是self注册的,还是superClass注册的,我们可以在 -addObserver:forKeyPath:options:context:和-removeObserver:forKeyPath:context。这两个方法中传入不同的context进行区分。
3> 如果当前类无法捕捉kvo,有可能是父类中捕捉,所以,添加上[super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 准没错
4> 触发kvo,可以是kvc,也可以是set方法
三、KVO原理实现
1、原理总纲
当某个类的实例对象的key第一次被观察时,系统就会在运行期动态地
创建该类的一个派生类NSKVONotifying_类名,
在这个派生类中重写该类中被观察的属性的 setter 方法。
2、验证原理
验证上述原理之前,我们需要了解下关于runtime的两个API
2.1 获取对象所属的类
2.2 设置对象所属类(修改对象的isa)
开始验证,拿 demo 验证下即可
当添加属性观察者的时候,系统动态创建Person的派生类NSKVONotifying_Person, 并修改jack的isa,使其指向这个派生类(也就是说,jack就变成是NSKVONotifying_Person这个派生类的实例)。这样修改后,当 touch屏幕 修改属性的时候,调用的就是派生类重写的该属性的setter方法。
注意 : 由于这种继承方式的注入是在运行时而不是编译时实现的,如果给定的实例没有观察者,那么KVO不会有任何开销,因为此时根本就没有KVO代码存在。
3、手动实现原理
核心步骤如下 :
1> 生成一个Person类的派生类 Person_KVO。
2> 使当前对象的isa指向新的派生类,就会调用派生类的set方法。
3> 重写Person_KVO的setName方法,在set方法中拿到观察者(使用运行时关联观察者这个属性)。
4> 每次重写setName方法,都调用观察者的ml_observeValueForKeyPath方法。实现时刻监听。
上一篇: 设计模式系列 - 代理模式
下一篇: [求助]新人关于KVO问题请教,谢谢大家
推荐阅读
-
趋同步发博文和同步发微博的实现原理
-
Android使用CardView作为RecyclerView的Item并实现拖拽和左滑删除
-
使用JQuery和CSS模拟超链接的用户单击事件的实现代码_jquery
-
Linux下实现MySQL数据备份和恢复的命令使用全攻略_MySQL
-
iOS使用自带的UIViewController实现qq加号下拉菜单的功能(实例代码)
-
iOS中使用Fastlane实现自动化打包和发布
-
Java使用IO流实现音频的剪切和拼接
-
iOS中模态Model视图跳转和Push视图跳转的需求实现方法
-
iOS实现屏幕亮度和闪光灯控制的实例代码
-
iOS AVPlayer切换播放源实现连续播放和全屏切换的方法