参考篇:iOS-KVO浅谈
前言:本文简述KVO本质,如有错误请留言指正。
什么是KVO?
KVO的全称是Key-Value Observing,俗称“键值监听”,可以用于监听某个对象属性值的改变
KVO简单使用
- (void)viewDidLoad {
[super viewDidLoad];
self.person = [[Person alloc] init];
self.person.age = 10;
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:@"age改变了"];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.age +=1;
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
/*
* keyPath : 监听的属性
* object : 监听的哪个对象
* change : 根据监听的结构体,展示监听的内容
* context : 监听传入的context
*/
NSLog(@"keyPath:%@ object:%@ context:%@ change:%@",keyPath,object,context,change);
}
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"age"];
}
Q:iOS用什么实现对象的KVO,(KVO本质)
A:使用RuntimeAPI
- 利用RuntimeAPI动态生成一个子类,并且让instance对象的isa指向这个全新的子类
- 当修改instance对象的属性时,会调用Foundation的
_NSSetXXXValueAndNotify
函数
willChangeValueForKey:
父类原来的setter
didChangeValueForKey:
内部会触发监听器(Oberser)的监听方法( observeValueForKeyPath:ofObject:change:context:)
代码验证:
self.person = [[Person alloc] init];
self.person.age = 10;
self.person1 = [[Person alloc] init];
self.person1.age = 10;
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:@"age改变了"];
只对Person进行KVO监听,Person1没有做监听,LLDB验证他们的isa指针指向,可以看到被监听的实例对象的isa指针指向NSKVONotifying_Person
类而不是Person
类,未监听的实例对象isa指针依旧指向Person
类
(lldb) p self.person->isa
(Class) $0 = NSKVONotifying_Person
(lldb) p self.person1->isa
(Class) $1 = Person
1.被监听的实例对象结构体指向
2.未被监听的实例对象结构体指向
伪代码展示NSKVONotifying_Person中都做了什么
-(void)setAge:(int)age{
[self willChangeValueForKey: age];
_age = age;
[self didChangeValueForKey:age];
}
- (void)didChangeValueForKey:(NSString *)key{
[self addObserver:self forKeyPath:@"age" options:@"传入的参数" context:@"传入的参数"];
}
小结
- Person的isa指针指向NSKVONotifying_Person
- Person1的isa指针指向Person
- NSKVONotifying_Person的isa指针指向NSKVONotifying_Person的元类
示例代码:
NSLog(@"Person:%p Person1:%p",[self.person methodForSelector:@selector(setAge:)],[self.person1 methodForSelector:@selector(setAge:)]);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew |NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:@"age改变了"];
NSLog(@"Person:%p Person1:%p",[self.person methodForSelector:@selector(setAge:)],[self.person1 methodForSelector:@selector(setAge:)]);
输出结果:
Person:0x10e5aeae0 Person1:0x10e5aeae0
Person:0x10e954f8e Person1:0x10e5aeae0
LLDB调试
(lldb) p (IMP)0x10e5aeae0
(IMP) $0 = 0x000000010e5aeae0 (test`-[Person setAge:] at Person.h:12)
(lldb) p (IMP)0x10e954f8e
(IMP) $1 = 0x000000010e954f8e (Foundation`_NSSetIntValueAndNotify)
示例代码
NSLog(@"Person-%p,Person-%p",object_getClass(self.person),object_getClass(self.person1));//self.person.isa
NSLog(@"Meta-%p,Meta1-%p",object_getClass(object_getClass(self.person)),object_getClass(object_getClass(self.person1)));//self.person.isa.isa
输出结果:
Person-0x604000114c70,Person-0x109c10258
Meta-0x604000114880,Meta1-0x109c10230
Q: NSKVONotifying_Person的isa指向哪里?
A:NSKVONotifying_Person的isa指向NSKVONotifying_Person的元类
Q:简述NSKVONotifying_Person类还包含的class
、dealloc
、_isKVOA
方法
// 屏幕内部实现,隐藏了NSKVONotifying_Person类的存在
- (Class)class
{
return [Person class];
}
- (void)dealloc{
//监听方法的收尾工作
}
//该类是否使用了KVO
- (BOOL)_isKVOA
{
return YES;
}
代码验证:
NSLog(@"Person-%@,Person-%@",object_getClass(self.person),[self.person class]);//self.person.isa
输出结果为:
NSKVONotifying_Person,Person
虽然self.person的isa指针指向NSKVONotifying_Person,但是[self.person class]依旧是Person,因为NSKVONotifying_Person是Person的子类且重写了class方法。
Q:如何知道NSKVONotifying_Person包含class、dealloc、_isKVOA等方法
A:代码实现
代码验证:
- (void)printMethodNameOfClass:(Class)class{
unsigned int count;
Method *methodList = class_copyMethodList(class, &count);
NSMutableString *methodNames = [NSMutableString string];
for (int i=0; i<count; i++) {
Method method = methodList[i];
NSString *str = NSStringFromSelector(method_getName(method));
[methodNames appendString:str];
[methodNames appendString:@" , "];
}
free(methodList);
NSLog(@"%@ : %@",class,methodNames);
}
[self printMethodNameOfClass:object_getClass(self.person)];
[self printMethodNameOfClass:object_getClass(self.person1)];
输出结果
NSKVONotifying_Person : setAge: , class , dealloc , _isKVOA ,
Person : setAge: , age ,
Q:如何手动出发KVO?
手动调用willChangeValueForKey:和didChangeValueForKey:
Q:直接修改成员变量会触发KVO么?
不会触发KVO
Q:通过KVC修改属性会触发KVO么?
会触发KVO