KVO的原理是什么?底层是如何实现的?
KVO是Key-value observing的缩写。
KVO是Objective-C是使用观察者设计模式实现的。
Apple使用了isa混写(isa-swizzling)来实现KVO。
我们可以通过代码去探索一下。
创建自定义类:XGPerson
@interface XGPerson : NSObject @property (nonatomic,assign) int age; @property (nonatomic,copy) NSString* name; @end
我们的思路就是看看对象添加KVO之前和之后有什么变化,是否有区别,代码如下:
@interface ViewController () @property (strong, nonatomic) XGPerson *person1; @property (strong, nonatomic) XGPerson *person2; @end - (void)viewDidLoad { [super viewDidLoad]; self.person1 = [[XGPerson alloc]init]; self.person2 = [[XGPerson alloc]init]; self.person1.age = 1; self.person2.age = 10; // 添加监听之前,获取类对象,通过两种方式分别获取 p1 和 p2的类对象 NSLog(@"before getClass--->> p1:%@ p2:%@",object_getClass(self.person1),object_getClass(self.person2)); NSLog(@"before class--->> p1:%@ p2:%@",[self.person1 class],[self.person2 class]); // 添加KVO监听 NSKeyValueObservingOptions option = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person1 addObserver:self forKeyPath:@"age" options:option context:nil]; // 添加监听之后,获取类对象 NSLog(@"after getClass--->> p1:%@ p2:%@",object_getClass(self.person1),object_getClass(self.person2)); NSLog(@"after class--->> p1:%@ p2:%@",[self.person1 class],[self.person2 class]); }
输出:
2018-11-02 15:16:13.276167+0800 KVO原理[4083:170379] before getClass--->> p1:XGPerson p2:XGPerson 2018-11-02 15:16:13.276271+0800 KVO原理[4083:170379] before class--->> p1:XGPerson p2:XGPerson 2018-11-02 15:16:13.276712+0800 KVO原理[4083:170379] after getClass--->> p1:NSKVONotifying_XGPerson p2:XGPerson 2018-11-02 15:16:13.276815+0800 KVO原理[4083:170379] after class--->> p1:XGPerson p2:XGPerson
从上面可以看出,object_getClass 和 class 方式分别获取到的 类对象竟然不一样,在对象添加了KVO之后,使用object_getClass的方式获取到的对象和我们自定义的对象不一样,而是NSKVONotifying_XGPerson,可以怀疑 class 方法可能被篡改了.
最终发现NSKVONotifying_XGPerson是使用Runtime动态创建的一个类,是XGPerson的子类.
看完对象,接下来我们来看下属性,就是被我们添加了KVO的属性age,我们要触发KVO回调就是去给age设置个值,那它肯定就是调用setAge这个方法.
下面监听下这个方法在被添加了KVO之后有什么不一样.
NSLog(@"person1添加KVO监听之前 - %p %p", [self.person1 methodForSelector:@selector(setAge:)], [self.person2 methodForSelector:@selector(setAge:)]); // 添加KVO监听 NSKeyValueObservingOptions option = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; [self.person1 addObserver:self forKeyPath:@"age" options:option context:nil]; NSLog(@"person1添加KVO监听之后 - %p %p", [self.person1 methodForSelector:@selector(setAge:)], [self.person2 methodForSelector:@selector(setAge:)]);
输出:
2018-11-02 15:16:13.276402+0800 KVO原理[4083:170379] person1添加KVO监听之前 - 0x10277c3e0 0x10277c3e0 2018-11-02 15:16:17.031319+0800 KVO原理[4083:170379] person1添加KVO监听之后 - 0x102b21f8e 0x10277c3e0
看输出我们能发现,在监听之前两个对象的方法所指向的物理地址都是一样的,添加监听后,person1对象的setAge方法就变了,这就说明一个问题,这个方法的实现变了,我们再通过Xcode断点调试打印看下到底调用什么方法
断点后,在调试器中使用 po 打印对象
(lldb) po [self.person1 methodForSelector:@selector(setAge:)]
(Foundation`_NSSetIntValueAndNotify)
(lldb) po [self.person2 methodForSelector:@selector(setAge:)]
(KVO原理`-[XGPerson setAge:] at XGPerson.m:13)