KVO
概述
KVO的全稱是NSKeyValueObserving,對象采用的一種非正式協定,當其他對象的指定屬性發生變化時,通知對象。由于KVO的機制,隻對對象的屬性起作用,一般繼承自NSObject都支援KVO。
您可以觀察任何對象屬性,包括簡單屬性、一對一關系和多對多關系。
使用
KVO使用通常需要三個步驟
- 通過
注冊觀察者,來觀察keyPath的變化addObserver:forKeyPath:options:context:
- 當相對于被觀察對象的指定鍵路徑的值發生變化時,通知觀察對象的
這個方法,觀察者應當實作該方法observeValueForKeyPath:ofObject:change:context:
- 當不再需要觀察對象屬性變化時,通過調用
或者removeObserver:forKeyPath:
阻止觀察者對象接收與接收此消息的對象相關的鍵路徑指定的屬性的更改通知.removeObserver:forKeyPath:context:
注:
addObserver
與
removeObserver
應當一一對應
注冊方法
在注冊時,
options
的值為
NSKeyValueObservingOptions
枚舉類型
。當為
NSKeyValueObservingOptionNew
、
NSKeyValueObservingOptionOld
時,表示接收新值和舊值,預設為隻接收新值。如果想在注冊觀察者後,立即接收一次回調,則可以加入
NSKeyValueObservingOptionInitial
枚舉
實作原理
KVO
是通過
isa-swizzling
技術實作的。在運作時根據原類建立一個中間類,這個中間類是原類的子類,并動态修改目前對象的
isa
指向中間類。并且将
class
方法重寫,傳回原類的
Class
。是以蘋果建議在開發中不應該依賴
isa
指針,而是通過
class
執行個體方法來擷取對象類型。
驗證
` _stu = [Student new];
_stu.stu_id = index;
NSLog(@"beforeClass:%@", object_getClass(_stu));
[_stu addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
NSLog(@"afterClass:%@", object_getClass(_stu));`
可以檢視輸出結果為:
`2018-12-30 14:34:01.761066+0800 KVO[1485:239369] beforeClass:Student`
2018-12-30 14:34:01.761533+0800 KVO[1485:239369] afterClass:NSKVONotifying_Student
自定義KVO
寫一個Student的分類
Student+KVO.h
`-(void)XK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context;`
Student+KVO.m
#import "NSObject+KVO.h"
#import <objc/runtime.h>
#import <objc/message.h>
@implementation Student (KVO)
-(void)XK_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options: (NSKeyValueObservingOptions)options context:(void *)context{
NSString* oldClassName = NSStringFromClass([self class]);
NSString* newClassName = [@"XKKVONotyfing_" stringByAppendingString:oldClassName];
const char * newName = [newClassName UTF8String];
Class newClass = objc_allocateClassPair([self class], newName, 0);
class_addMethod(newClass,@selector(setAge:), (IMP)setAge, "v@:i");
objc_registerClassPair(newClass);
object_setClass(self, newClass);
objc_setAssociatedObject(self,
(__bridge const void *)@"objc", observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}
void setAge(id self, SEL _cmd, int age) {
Class myClass = [self class];
object_setClass(self, class_getSuperclass([self class]));
//調用父類
id (*msgSend) (id, SEL,...) = (void *)objc_msgSend;
msgSend(self, @selector(setAge:),age);
id observer = objc_getAssociatedObject(self, (__bridge const void *)@"objc");
id (*msgSendObj) (id, SEL,id,...) = (void *)objc_msgSend;
msgSendObj(observer,@selector(XK_observeValueForKeyPath:ofObject:change:context:),@"age",@"age",nil,nil);
//改為子類
object_setClass(self, myClass);
}
觀察者中調用
<[_stu XK_addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew context:nil];
- (void)XK_observeValueForKeyPath:(NSString )keyPath ofObject:(id)object change:(NSDictionary)change context:(void *)context{
NSLog(@"keyPath:%@",keyPath);
NSLog(@"ofObject:%@",object);
NSLog(@"change:%@",change);
NSLog(@"afterClass:%@", object_getClass(_stu));
}</code></pre>