目录
基本使用
KVO的全称是Key-Value Observing,俗称键值监听,可以用于监听某个对象属性值的改变。
创建一个Person类
@interface Person : NSObject
@property (assign, nonatomic) int age;
@end
使用案例
#import "ViewController.h"
#import "Person.h"
@interface ViewController ()
@property (nonatomic, strong) Person * person1;//
@property (nonatomic, strong) Person * person2;//
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[Person alloc] init];
self.person1.age = 1;
self.person2 = [[Person alloc] init];
self.person2.age = 2;
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:(options) context:@"123"];
}
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context{
NSLog(@"object=%@",object);
NSLog(@"change=%@",change);
NSLog(@"context=%@",context);
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person1.age = 10;
self.person2.age = 20;
}
-(void)dealloc{
//一定要移除监听,否则引起内存泄漏
[self.person1 removeObserver:self forKeyPath:@"age"];
}
@end
每次点击屏幕的打印日志为
object=<Person: 0x13565c030>
change={
kind = 1;
new = 10;
old = 2;
}
context=123
分析
如上面的例子,我们在添加监听前打个断点,添加监听后打个断点,来看看person1和person2发生了什么变化。
//添加监听前:lldb调试
(lldb) po self.person1->isa
Person
(lldb) po self.person2->isa
Person
//添加监听后:lldb调试
(lldb) po self.person1->isa
NSKVONotifying_Person
(lldb) po self.person2->isa
Person
可以看出添加了监听的person1的isa指针的指向发生了变化。产生了一个新的类对象,NSKVONotifying_Person。
我们来分析下添加监听后person1->isa指向的类对象NSKVONotifying_Person。
工程中引入 NSObject+classInfo这个分类。NSObject+classInfo
#import "NSObject+classInfo.h"
//添加监听后调用
Class KVO_Person = object_getClass(self.person1);
[KVO_Person printInfoOfClass];
//打印的日志如下
----------- protocol -----------
----------- iVar -----------
----------- property -----------
----------- instanceMethod -----------
instanceMethodName-0 = setAge:
instanceMethodName-1 = class
instanceMethodName-2 = dealloc
instanceMethodName-3 = _isKVOA
----------- classMethod -----------
通过以上分析可知:
(监听前)perosn1->isa 指向 Person类对象 | (监听后)person1->isa 指向 NSKVONotifying_Person类对象 |
setAge: | setAge: |
age | class |
dealloc | |
_isKVOA |
下面再来看下的KVO_Person的superclass指向哪里,可以看出 KVO_Person 的父类是Person类对象。
(lldb) p/x [KVO_Person superclass]
(Class) $2 = 0x0000000100059f18 Person
(lldb) p/x [Person class]
(Class) $3 = 0x0000000100059f18 Person
NSKVONotifying_Person
#import "NSKVONotifying_MJPerson.h"
@implementation NSKVONotifying_Person
- (void)setAge:(int)age{
_NSSetIntValueAndNotify();
}
// 伪代码
void _NSSetIntValueAndNotify(){
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key{
// 通知监听器,某某属性值发生了改变
[oberser observeValueForKeyPath:key ofObject:self change:nil context:nil];
}
// 屏幕内部实现,隐藏了NSKVONotifying_MJPerson类的存在
- (Class)class{
return [MJPerson class];
}
- (void)dealloc{
// 收尾工作
}
- (BOOL)_isKVOA{
return YES;
}
@end
补充
手动触发监听的方法
[self.person1 willChangeValueForKey:@"age"];
[self.person1 didChangeValueForKey:@"age"];
//或者
self.person1.age = self.person1.age;
行者常至,为者常成!