iOS之KVO实现原理探究
2018-09-07 本文已影响53人
内心戏十足的伪胖子
KVO,就是key-value-observing,键值观察者模式。开发中经常会使用到,并且面试大概率问到其底层实现原理。
用法
eg:
#import <Foundation/Foundation.h>
@interface Programmer : NSObject
@property (copy, nonatomic) NSString *name;
@end
#import "Programmer.h"
@implementation Programmer
- (void)setName:(NSString *)name {
_name = name;
}
@end
#import "ViewController.h"
#import "Programmer.h"
#import <objc/runtime.h>
@interface ViewController ()
@property (strong, nonatomic) Programmer *programmer1;
@property (strong, nonatomic) Programmer *programmer2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.programmer1 = [[Programmer alloc] init];
self.programmer1.name = @"Bob";
self.programmer2 = [[Programmer alloc] init];
self.programmer2.name = @"Lili";
NSLog(@"添加监听之前person1的isa指针指向%@",object_getClass(self.programmer1));//在调试模式下可以直接 po self.person1->isa
NSLog(@"添加监听之前person类对象的isa指针指向%@",object_getClass(object_getClass(self.programmer1)));
NSLog(@"添加监听之前person1的setName方法的地址%p",[self.programmer1 methodForSelector:@selector(setName:)]);//在调试模式下,通过 p (IMP)地址 打印出这个IMP地址对应的方法名称
[self.programmer1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
NSLog(@"添加监听之后person1的isa指针指向%@",object_getClass(self.programmer1));
NSLog(@"添加监听之后person类对象的isa指针指向%@",object_getClass(object_getClass(self.programmer1)));
NSLog(@"添加监听之后person1的setName方法的地址%p",[self.programmer1 methodForSelector:@selector(setName:)]);
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.programmer1.name = @"Mark";
self.programmer2.name = @"Jeny";
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"keyPath===%@",keyPath);
NSLog(@"object===%@",object);
NSLog(@"change===%@",change);
NSLog(@"context===%@",context);
}
- 定义一个
Programmer
类,有一个name
的property - 然后在控制器中有一个
@property (strong, nonatomic) Programmer *programmer1;
- 在ViewDidLoad中添加观察,
[self.programmer1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
,NSKeyValueObservingOptionNew和NSKeyValueObservingOptionOld分别表示变化之前的值和改变之后的值 - 在监听的控制器中实现
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
这个方法,其中change为一个字典,里面包含新值和旧值
原理
上面的例子中:
-
Programmer
类的实例programmer1
的name
属性被控制器监听了。这时,OC的runtime机制生成了一个KVONotifying_ Programmer
的类 -
programmer1
实例的isa指针从指向Programmer
的类对象,变成指向KVONotifying_ Programmer
的类对象,而KVONotifying_ Programmer
的isa指针指的是KVONotifying_ Programmer
的meta-class元类对象,KVONotifying_ Programmer
的superclass指针指的是Programmer
的类对象 - 修改
programmer1
的name
属性的时候调用了Foundation
框架下的一个_NSSetObjectValueAndNotify
方法 -
KVONotifying_ Programmer
类重写了Programmer
类属性name的setter方法加入了NSObject的两个方法:willChangeValueForKey:(值改变之前)和didChangevlueForKey:(值改变之后)。在一个被观察属性发生改变之前,willChangeValueForKey:一定会被调用,这就会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而observeValueForKey:ofObject:change:context:也会被调用。
探究
待续
如果有错误,感谢各位大佬指正。