iOS 用什么方式实现对一个对象的KVO?(KVO的本质是什么)
- 利用Runtime API动态生成一个子类, 并且让instance对象的isa指向这个全新的子类.
- 当修改instance对象的属性时, 会调用Foundation 的 _NSSetXXValueAndNotify函数,这里面包括
willChangeValueForKey:
父类原来的Setter
didChangeValueForKey:
在didChangeValueForKey:中 会触发监听器(Observer)的监听方法observeValueForKeyPath:OfObject:change:context:)
如何手动触发KVO?
- 手动调用willChangeValueForKey:, didChangeValueForKey:两者
- 若只调用didChangeValueForKey:, 则无法触发, 需两者一起调用
KVO的基本使用
1. 创建一个类World
@interface World : NSObject
@property(nonatomic,assign) NSInteger number;
@end
2. 在控制器中
self.world = [World new];
// 给对象添加KVO监听, 其 isa 会变成 NSKVONotifying_World, 而不是 World
[self.world addObserver:self
forKeyPath:@"number"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:@"123"];
// 默认为0
self.world.number = 1;
// 当监听 对象的属性 的值改变时调用
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"number"]) {
NSLog(@"监听到%@的%@属性值发生改变%@ - %@",object, keyPath, change, context);
}
}
KVO解析
-(void)testKVO
{
self.world = [World new];
self.world2 = [World new];
// KVO之前
NSLog(@"%p - %p",[self.world methodForSelector:@selector(setNumber:)],
[self.world2 methodForSelector:@selector(setNumber:)]);
// 给对象添加KVO监听, 其 isa 会变成 NSKVONotifying_World, 而不是 World
[self.world addObserver:self
forKeyPath:@"number"
options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew
context:@"123"];
// KVO之前
NSLog(@"%p - %p",[self.world methodForSelector:@selector(setNumber:)],
[self.world2 methodForSelector:@selector(setNumber:)]);
}
打印两个对象的isa 做对比
![](https://img.haomeiwen.com/i1208639/77d2dbbad06267d6.png)
这里用MJPerson类 做类比
![](https://img.haomeiwen.com/i1208639/ddd6335a342b9d8c.png)
![](https://img.haomeiwen.com/i1208639/c3a2473a0c1ed6af.png)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// self.world对象的isa 为 NSKVONotifying_World, 不是World
// NSKVONotifying_World 是系统利用Runtime机制动态创建的一个类
[self.world setNumber:11];
// 调用的是 Foundation`_NSSetLongLongValueAndNotify)
// 在这里面会调用bserveValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
[self.world2 setNumber:22];
// 调用的是 TestKVO`-[World setNumber:] at World.m:13)
}
![](https://img.haomeiwen.com/i1208639/5921f84548a8a6f2.png)
网友评论