KVO是Key-Value Observing
的簡寫,稱爲鍵值觀察
,用來監聽對象屬性的變化
。markdown
咱們以IFPerson
類爲例研究KVO
的使用。函數
KVO
監聽- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.person.name = @"kvo";
NSLog(@"class = %@",object_getClass(self.person));
//對person的name添加kvo監聽
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
NSLog(@"class = %@",object_getClass(self.person));
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.person.name = [NSString stringWithFormat:@"%@ +",self.person.name];
}
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"name"]) {
NSLog(@" === %@",change);
}
}
-(void)dealloc {
//移除監聽
[self.person removeObserver:self forKeyPath:@"name"];
}
複製代碼
運行代碼點擊屏幕效果以下spa
KVO
監聽若是想要手動觸發KVO
監聽,須要重寫+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
函數,該函數默認返回YES,表示自動觸發,返回NO,則表示手動觸發
。對於本文,咱們判斷下key == name
則手動觸發,不然自動觸發。修改代碼以下3d
//在`IFPerson.m`中
+(BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([key isEqualToString:@"name"]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
-(void)setName:(NSString *)name {
[self willChangeValueForKey:name];
_name = name;
[self didChangeValueForKey:name];
}
複製代碼
從上述代碼中,咱們能夠看到,要實現手動觸發KVO
,有兩個步驟須要實現指針
步驟一
:重寫+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key
函數,根據須要返回NO步驟二
:對於監聽的對象屬性發生改變時,須要在改變先後調用willChangeValueForKey:
和didChangeValueForKey:
KVO
底層原理探究從上述中咱們能夠看到咱們能夠根據KVO
監聽屬性的變化,那麼在底層是怎麼實現的呢?code
KVO
監聽先後對象的變化咱們在監聽name
先後獲取person
的class以及其指針地址
看下是否有變化,代碼以下orm
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.person.name = @"kvo";
NSLog(@"class = %@ address = %p",object_getClass(self.person),self.person);
//對person的name添加kvo監聽
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
NSLog(@"class = %@ address = %p",object_getClass(self.person),self.person);
}
複製代碼
打印結果以下從上圖中咱們看到,
self.person
的class
先後發生了改變,可是對象地址並無改變。咱們再經過獲取isa
來驗證下。server
KVO
監聽後,對象的class即isa指向發生了改變
,由IFPerson
變成了NSKVONotifying_IFPerson
,可是對象的地址沒有改變
。NSKVONotifying_IFPerson
是什麼?咱們知道了在進行KVO
監聽後class
變成了NSKVONotifying_IFPerson
,那麼NSKVONotifying_IFPerson
從何而來?下面咱們看下NSKVONotifying_IFPerson
的父類是誰。對象
因爲superClass
是隱藏屬性,在外部沒法訪問,由於咱們定義一個結構體,並強轉下類型來得到superClass
,代碼以下繼承
struct cw_objc_class {
Class _Nonnull isa;
Class _Nullable super_class;
};
// 強轉一下類型
struct cw_objc_class *newClass = (__bridge struct cw_objc_class *)object_getClass(self.person);
NSLog(@"superClass = %@",newClass->super_class);
複製代碼
從打印結果來看
NSKVONotifying_IFPerson
的父類居然是IFPerson
。
NSKVONotifying_IFPerson
類實現了哪些方法?既然runtime
在進行KVO
監聽的時候派生了NSKVONotifying_IFPerson
類,那麼NSKVONotifying_IFPerson
類跟IFPerson
類實現方法有什麼區別呢?咱們打印下監聽先後兩個類的實現方法區別
#pragma mark - 遍歷方法-ivar-property
- (void)printClassAllMethod:(Class)cls{
unsigned int count = 0;
Method *methodList = class_copyMethodList(cls, &count);
for (int i = 0; i<count; i++) {
Method method = methodList[i];
SEL sel = method_getName(method);
IMP imp = class_getMethodImplementation(cls, sel);
NSLog(@"%@-%p",NSStringFromSelector(sel),imp);
}
free(methodList);
}
NSLog(@"監聽以前方法:");
[self printClassAllMethod:object_getClass(self.person)];
//對person的name添加kvo監聽
[self.person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:NULL];
NSLog(@"監聽以前方法:");
[self printClassAllMethod:object_getClass(self.person)];
複製代碼
打印結果以下從打印監聽先後方法列表咱們能夠看到
NSKVONotifying_IFPerson
類有以下變化
setter
方法,如setName:isa
指針指向的地址,由原來的IFPerson
指向了NSKVONotifying_IFPerson
dealloc
方法_isKOA
標識符isa
指針什麼時候從新指向IFPerson
?既然isa
指針在被監聽後指向了NSKVONotifying_IFPerson
類,那麼什麼時候從新指向IFPerson
呢?答案是在移除監聽的時候
。爲了驗證,咱們在dealloc
中打印下監聽先後isa
指針的變化。
KVO
總結KVO
在監聽屬性後,runtime會根據對象的類型生成一個NSKVONotifying_XXX(XXX是原來的類)
的派生類
,該派生類NSKVONotifying_XXX
繼承於原來的類
NSKVONotifying_XXX
重寫被監聽屬性的setter
方法,可是賦值
的時候經過重寫的setter
內部消息轉發仍是經過原來類的setter
方法來實現KVO
在移除監聽時將isa
指針指向地址由NSKVONotifying_XXX
派生類從新指向原來類