一、KVO基本用法
self.person = [[Peerson alloc]init];
self.person.age = 10;
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person addObserver:self forKeyPath:@"age" options:options context:nil];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.person.age = 20;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
}
- (void)dealloc{
[self.person removeObserver:self forKeyPath:@"age"];
}
二、KVO本质分析
1.为何监听后会变化
self.person1 = [[Peerson alloc]init];
self.person1.age = 1;
self.person2 = [[Peerson alloc]init];
self.person2.age = 2;
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self.person1 setAge:20];
[self.person2 setAge:30];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
NSLog(@"监听到%@的%@属性值发生了改变%@", object, keyPath, change);
}
为什么上面代码能监听到person1的改变而监听不到person2改变????
2.分析发生变化
此时在上述代码中打印一下person1的isa和person2的isa
我们会发现 self.person1.isa = NSKVONotifying_MJPerson, self.person2.isa = MJPerson 我们知道,实例对象指针指向的位置,就是类对象,说明self.person1的类对象是NSKVONotifying_MJPerson已经不是原来的MJPerson NSKVONotifying_MJPerson这个类对象其实,是runtime动态创建出来的,并且它是继承MJPerson类的 用关系图表示: 而NSKVONotifying_MJPerson类对象中,肯定有isa,superclass,setAge(其它的暂时不用看) 当调用self.person1.age = 20的时候,
- 会先创建NSKVONotifying_MJPerson类继承MJPerson
- 里面会有一个setAge方法,
- setAge方法中调用了Foundation中的_NSSetIntValueNotify方法
大概用伪代码来实现一下
#import "MJPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface NSKVONotifying_MJPerson : MJPerson
@end
NS_ASSUME_NONNULL_END
#import "NSKVONotifying_MJPerson.h"
@implementation NSKVONotifying_MJPerson
- (void)setAge:(int)age{
_NSSetIntValueNotify();
}
void NSSetIntValueNotify(){
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key{
[observer observerValueForKeyPath:key ofObject:self change:nil context:nil];
}
@end
而self.person2的指针指向MJPerson类对象,直接从MJPerson类对象中找到setAge方法实现就可以了. 用图看一下就是这样:
三、KVO本质的验证
看看添加之前和添加之后,它们的类对象有哪些变化
#import <objc/runtime.h>
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[MJPerson alloc]init];
self.person1.age = 1;
self.person1.height = 11;
self.person2 = [[MJPerson alloc]init];
self.person2.age = 2;
self.person2.height = 22;
NSLog(@"person1添加kvo监听之前类对象 %@ - %@", object_getClass(self.person1),
object_getClass(self.person2));
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"person1添加kvo监听之后类对象 %@ - %@", object_getClass(self.person1),
object_getClass(self.person2));
}
添加之前类对象是MJPerson,执行的是MJPerson中setAge方法 添加以后person1类对象是NSKVONotifying_MJPerson,那么究竟执行的NSKVONotifying_MJPerson中哪个方法
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[MJPerson alloc]init];
self.person1.age = 1;
self.person1.height = 11;
self.person2 = [[MJPerson alloc]init];
self.person2.age = 2;
self.person2.height = 22;
NSLog(@"person1添加kvo监听之前执行的方法 %p -%p", [self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]);
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:nil];
NSLog(@"person1添加kvo监听之后执行的方法 %p -%p", [self.person1 methodForSelector:@selector(setAge:)],
[self.person2 methodForSelector:@selector(setAge:)]);
}
可以看到:添加kvo之前person1和person2都是执行的同一个方法0x1061d8da0, 添加kvo以后person1执行的是0x7fff207b1cfb这个方法 那么如何查看0x1061d8da0对应的具体方法??? 通过p (IMP)0x1061d8da0转换,如图
NSKVONotifying_MJPerson的isa指针指向自己的元类,meta-NSKVONotifying_MJPerson
四、窥探KVO本质执行顺序
至于判断Foundation中有没有NSSetIntValueAndNotify方法,只能通过反编译查看。这里不做详细介绍 前面提到说_NSSetIntValueNotify中先调用willChangeValueForKey在调用setAge:age在调用didChangeValueForKey,然后didChangeValueForKey中调用observerValueForKeyPath:key,方法进行验证一下
- (void)setAge:(int)age{
_NSSetIntValueNotify();
}
void NSSetIntValueNotify(){
[self willChangeValueForKey:@"age"];
[super setAge:age];
[self didChangeValueForKey:@"age"];
}
- (void)didChangeValueForKey:(NSString *)key{
[observer observerValueForKeyPath:key ofObject:self change:nil context:nil];
}
我们在person中重新写 willChangeValueForKey这些方法,看看执行顺序如图
五、获取NSKVONotifying_MJPerson中类方法
- (void)printMethodNamesOfClass:(Class)cls{
unsigned int count;
Method *methodList = class_copyMethodList(cls, &count);
NSMutableString *methodNames = [NSMutableString string];
for (int i = 0; i < count; i++) {
Method method = methodList[i];
NSString *methodName = NSStringFromSelector(method_getName(method));
[methodNames appendString:methodName];
[methodNames appendString:@", "];
}
free(methodList);
NSLog(@"%@ %@", cls, methodNames);
}
- (void)viewDidLoad {
[super viewDidLoad];
self.person1 = [[MJPerson alloc] init];
self.person1.age = 1;
self.person2 = [[MJPerson alloc] init];
self.person2.age = 2;
NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld;
[self.person1 addObserver:self forKeyPath:@"age" options:options context:@"123"];
[self printMethodNamesOfClass:object_getClass(self.person1)];
[self printMethodNamesOfClass:object_getClass(self.person2)];
}
从打印结果就可以看出NSKVONotifying_MJPerson中包含了setAge:, class, dealloc, _isKVOA这些方法 如果给peson1添加kvo那么它的指针其实已经指向了NSKVONotifying_MJPerson,调用的方法也是NSKVONotifying_MJPerson中的setAge
class:重写了父类的class,当你用[self.person1 class]获取类对象的时候还是MJPerson,而不是 NSKVONotifying_MJPerson,为了就是让你不知道它的存在,蒙蔽你 dealloc:方法中做了一些销毁,收尾工作,比如动态创建的类,需要销毁 _isKVOA: 应该是返回YES
下面附上一张面试题:
|