KVO,就是key-value-observing,鍵值觀察者模式。開發(fā)中經(jīng)常會使用到,并且面試大概率問到其底層實現(xiàn)原理。
用法
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(@"添加監(jiān)聽之前person1的isa指針指向%@",object_getClass(self.programmer1));//在調(diào)試模式下可以直接 po self.person1->isa
NSLog(@"添加監(jiān)聽之前person類對象的isa指針指向%@",object_getClass(object_getClass(self.programmer1)));
NSLog(@"添加監(jiān)聽之前person1的setName方法的地址%p",[self.programmer1 methodForSelector:@selector(setName:)]);//在調(diào)試模式下,通過 p (IMP)地址 打印出這個IMP地址對應(yīng)的方法名稱
[self.programmer1 addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:nil];
NSLog(@"添加監(jiān)聽之后person1的isa指針指向%@",object_getClass(self.programmer1));
NSLog(@"添加監(jiān)聽之后person類對象的isa指針指向%@",object_getClass(object_getClass(self.programmer1)));
NSLog(@"添加監(jiān)聽之后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分別表示變化之前的值和改變之后的值 - 在監(jiān)聽的控制器中實現(xiàn)
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context這個方法,其中change為一個字典,里面包含新值和舊值
原理
上面的例子中:
-
Programmer類的實例programmer1的name屬性被控制器監(jiān)聽了。這時,OC的runtime機制生成了一個KVONotifying_ Programmer的類 -
programmer1實例的isa指針從指向Programmer的類對象,變成指向KVONotifying_ Programmer的類對象,而KVONotifying_ Programmer的isa指針指的是KVONotifying_ Programmer的meta-class元類對象,KVONotifying_ Programmer的superclass指針指的是Programmer的類對象 - 修改
programmer1的name屬性的時候調(diào)用了Foundation框架下的一個_NSSetObjectValueAndNotify方法 -
KVONotifying_ Programmer類重寫了Programmer類屬性name的setter方法加入了NSObject的兩個方法:willChangeValueForKey:(值改變之前)和didChangevlueForKey:(值改變之后)。在一個被觀察屬性發(fā)生改變之前,willChangeValueForKey:一定會被調(diào)用,這就會記錄舊的值。而當改變發(fā)生后,didChangeValueForKey:會被調(diào)用,繼而observeValueForKey:ofObject:change:context:也會被調(diào)用。
探究
待續(xù)
如果有錯誤,感謝各位大佬指正。