KVO不像通知機制那樣通過一個通知中心通知所有觀察者對象坯墨,而是在對象屬性變化時通知會被直接發(fā)送給觀察者對象.KVO機制解析圖:
KVO(Key-Value Observing)
KVO(Key-Value Observing) 是 Objective-C 對觀察者模式(Observer Pattern)的實現(xiàn)寂汇。也是 Cocoa Binding 的基礎。當被觀察對象的某個屬性發(fā)生更改時捣染,觀察者對象會獲得通知骄瓣。
KVO內部實現(xiàn)原理
二話不說,直接擼起袖子就是干耍攘。
#import "Person.h"
#import <objc/runtime.h>
@implementation Person
-(void)printInfo{
NSLog(@"isa:%@,supperclass:%@",NSStringFromClass(object_getClass(self)),
class_getSuperclass(object_getClass(self)));
NSLog(@"age setter function pointer:%p", class_getMethodImplementation(object_getClass(self), @selector(setAge:)));
NSLog(@"printInfo function pointer:%p", class_getMethodImplementation(object_getClass(self), @selector(printInfo)));
}
@end
然后我們進行對Person屬性進行監(jiān)聽榕栏,看看監(jiān)聽前后的打印變化:
static NSString *privateKVOContext = @"privateKVOContext";
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Person *person = [[Person alloc]init];
NSLog(@"Before add observer————————————————————————–");
[person printInfo];
[person addObserver:self forKeyPath:@"age" options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:&privateKVOContext];
NSLog(@"After add observer————————————————————————–");
[person printInfo];
[person removeObserver:self forKeyPath:@"age"];
NSLog(@"After remove observer————————————————————————–");
[person printInfo];
}
輸出結果:
2018-08-23 10:29:54.631956+0800 KVO原理解析-18-8-23-0[1448:53790] Before add observer————————————————————————–
2018-08-23 10:29:54.632077+0800 KVO原理解析-18-8-23-0[1448:53790] isa:Person,supperclass:NSObject
2018-08-23 10:29:54.632199+0800 KVO原理解析-18-8-23-0[1448:53790] age setter function pointer:0x10899e510
2018-08-23 10:29:54.632339+0800 KVO原理解析-18-8-23-0[1448:53790] printInfo function pointer:0x10899e420
2018-08-23 10:29:54.632560+0800 KVO原理解析-18-8-23-0[1448:53790] After add observer————————————————————————–
2018-08-23 10:29:54.632673+0800 KVO原理解析-18-8-23-0[1448:53790] isa:NSKVONotifying_Person,supperclass:Person
2018-08-23 10:29:54.632729+0800 KVO原理解析-18-8-23-0[1448:53790] age setter function pointer:0x108cdea7a
2018-08-23 10:29:54.632780+0800 KVO原理解析-18-8-23-0[1448:53790] printInfo function pointer:0x10899e420
2018-08-23 10:29:54.632876+0800 KVO原理解析-18-8-23-0[1448:53790] After remove observer————————————————————————–
2018-08-23 10:29:54.632930+0800 KVO原理解析-18-8-23-0[1448:53790] isa:Person,supperclass:NSObject
2018-08-23 10:29:54.633004+0800 KVO原理解析-18-8-23-0[1448:53790] age setter function pointer:0x10899e510
2018-08-23 10:29:54.633051+0800 KVO原理解析-18-8-23-0[1448:53790] printInfo function pointer:0x10899e420
通過輸出結果分析:在對Person的屬性age添加KVO之后,系統(tǒng)通過runtime動態(tài)的創(chuàng)建一個派生類NSKVONotifying_Person蕾各,而根據(jù)class_getSuperclass得到的結果竟然是Person扒磁,然后age是使我們KVO需要觀察的屬性,它的setter函數(shù)指針變了式曲。而我們也知道渗磅,所謂的OC的消息機制是通過isa去查找實現(xiàn)的,那么我們可以得到一下結論:
KVO是基于runtime機制實現(xiàn)的
當某個類的屬性對象
第一次被觀察
時检访,系統(tǒng)就會在運行期動態(tài)
地創(chuàng)建該類的一個派生類
始鱼,在這個派生類中重寫基類中任何被觀察屬性的setter 方法。派生類在被重寫的setter方法內實現(xiàn)真正的通知機制
如果原類為Person脆贵,那么生成的派生類名為
NSKVONotifying_Person
每個類對象中都有一個isa指針指向當前類医清,當一個類對象的第一次被觀察,那么系統(tǒng)會偷偷將isa指針指向動態(tài)生成的派生類卖氨,從而在給被監(jiān)控屬性賦值時執(zhí)行的是派生類的setter方法
鍵值觀察通知依賴于NSObject 的兩個方法:
willChangeValueForKey:
和didChangevlueForKey:
会烙;在一個被觀察屬性發(fā)生改變之前,willChangeValueForKey:
一定會被調用筒捺,這就 會記錄舊的值柏腻。而當改變發(fā)生后,didChangeValueForKey:
會被調用系吭,繼而observeValueForKey:ofObject:change:context:
也會被調用五嫂。補充:KVO的這套實現(xiàn)機制中蘋果還偷偷重寫了class方法,讓我們誤認為還是使用的當前類肯尺,從而達到隱藏生成的派生類沃缘。
自定義KVO
主要參考KVOController
#import <Foundation/Foundation.h>
typedef void(^KVOObserveBlk)(NSString *keyPath,id observeObj,NSDictionary *valueChange);
@interface ZQKVOController : NSObject
+(instancetype)controllerWithObserver:(nullable id)observer;
@property (nullable, nonatomic, weak, readonly) id observer;
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(KVOObserveBlk)block;
@end
#import "ZQKVOController.h"
#import <pthread.h>
#pragma mark Utilities -
typedef NS_ENUM(uint8_t, ZQKVOInfoState) {
ZQKVOInfoStateInitial = 0,/** 初始化 */
ZQKVOInfoStateObserving,/** 監(jiān)聽 */
ZQKVOInfoStateNotObserving,/** 為監(jiān)聽 */
};
NSString *const ZQKVONotificationKeyPathKey = @"FBKVONotificationKeyPathKey";
@interface ZQKVOInfo : NSObject
@end
@implementation ZQKVOInfo
{
@public
KVOObserveBlk _block;
__weak ZQKVOController *_controller;
NSString *_keyPath;
NSKeyValueObservingOptions _options;
void *_context;
ZQKVOInfoState _state;
}
- (instancetype)initWithController:(ZQKVOController *)controller
keyPath:(NSString *)keyPath
options:(NSKeyValueObservingOptions)options
block:(nullable KVOObserveBlk)block
context:(nullable void *)context
{
self = [super init];
if (nil != self) {
_controller = controller;
_block = [block copy];
_keyPath = [keyPath copy];
_options = options;
_context = context;
}
return self;
}
@end
@interface ZQKVOSharedController : NSObject
/** */
+ (instancetype)sharedController;
/** 添加監(jiān)聽 */
- (void)observe:(id)object info:(nullable ZQKVOInfo *)info;
/** 取消監(jiān)聽 */
- (void)unobserve:(id)object infos:(nullable NSSet *)infos;
@end
@implementation ZQKVOSharedController
{
NSHashTable<ZQKVOInfo *> *_infos;
pthread_mutex_t _mutex;/** 互斥鎖 */
}
+ (instancetype)sharedController
{
static ZQKVOSharedController *_controller = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_controller = [[ZQKVOSharedController alloc] init];
});
return _controller;
}
- (instancetype)init
{
self = [super init];
if (nil != self) {
NSHashTable *infos = [NSHashTable alloc];
#ifdef __IPHONE_OS_VERSION_MIN_REQUIRED
_infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
if ([NSHashTable respondsToSelector:@selector(weakObjectsHashTable)]) {
_infos = [infos initWithOptions:NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
} else {
// silence deprecated warnings
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
_infos = [infos initWithOptions:NSPointerFunctionsZeroingWeakMemory|NSPointerFunctionsObjectPointerPersonality capacity:0];
#pragma clang diagnostic pop
}
#endif
pthread_mutex_init(&_mutex, NULL);
}
return self;
}
- (void)observe:(id)object info:(nullable ZQKVOInfo *)info
{
if (nil == info) {
return;
}
// register info
pthread_mutex_lock(&_mutex);
[_infos addObject:info];
pthread_mutex_unlock(&_mutex);
// add observer
[object addObserver:self forKeyPath:info->_keyPath options:info->_options context:(void *)info];
if (info->_state == ZQKVOInfoStateInitial) {
info->_state = ZQKVOInfoStateObserving;
} else if (info->_state == ZQKVOInfoStateNotObserving) {
/** 移除相同路徑的監(jiān)聽 */
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
}
- (void)unobserve:(id)object infos:(nullable NSSet<ZQKVOInfo *> *)infos
{
if (0 == infos.count) {
return;
}
// unregister info
pthread_mutex_lock(&_mutex);
for (ZQKVOInfo *info in infos) {
[_infos removeObject:info];
}
pthread_mutex_unlock(&_mutex);
for (ZQKVOInfo *info in infos) {
if (info->_state == ZQKVOInfoStateObserving) {
[object removeObserver:self forKeyPath:info->_keyPath context:(void *)info];
}
info->_state = ZQKVOInfoStateNotObserving;
}
}
- (void)observeValueForKeyPath:(nullable NSString *)keyPath
ofObject:(nullable id)object
change:(nullable NSDictionary<NSKeyValueChangeKey, id> *)change
context:(nullable void *)context
{
NSAssert(context, @"missing context keyPath:%@ object:%@ change:%@", keyPath, object, change);
ZQKVOInfo *info;
{
// lookup context in registered infos, taking out a strong reference only if it exists
pthread_mutex_lock(&_mutex);
info = [_infos member:(__bridge id)context];
pthread_mutex_unlock(&_mutex);
}
if (nil != info) {
// take strong reference to controller
ZQKVOController *controller = info->_controller;
if (nil != controller) {
// take strong reference to observer
id observer = controller.observer;
if (nil != observer) {
if (info->_block) {
info->_block(keyPath, object, change);
} else {
[observer observeValueForKeyPath:keyPath ofObject:object change:change context:info->_context];
}
}
}
}
}
- (void)dealloc
{
pthread_mutex_destroy(&_mutex);
}
@end
@implementation ZQKVOController
{
NSMapTable<id, NSMutableSet<ZQKVOInfo *> *> *_objectInfosMap;
pthread_mutex_t _lock;
}
+ (instancetype)controllerWithObserver:(nullable id)observer
{
return [[self alloc] initWithObserver:observer retainObserved:YES];
}
- (instancetype)initWithObserver:(nullable id)observer retainObserved:(BOOL)retainObserved
{
self = [super init];
if (nil != self) {
_observer = observer;
NSPointerFunctionsOptions keyOptions = retainObserved ? NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPointerPersonality : NSPointerFunctionsWeakMemory|NSPointerFunctionsObjectPointerPersonality;
_objectInfosMap = [[NSMapTable alloc] initWithKeyOptions:keyOptions valueOptions:NSPointerFunctionsStrongMemory|NSPointerFunctionsObjectPersonality capacity:0];
pthread_mutex_init(&_lock, NULL);
}
return self;
}
#pragma mark Utilities -
- (void)observe:(id)object info:(ZQKVOInfo *)info
{
pthread_mutex_lock(&_lock);
NSMutableSet *infos = [_objectInfosMap objectForKey:object];
// check for info existence
ZQKVOInfo *existingInfo = [infos member:info];
if (nil != existingInfo) {
pthread_mutex_unlock(&_lock);
return;
}
// lazilly create set of infos
if (nil == infos) {
infos = [NSMutableSet set];
[_objectInfosMap setObject:infos forKey:object];
}
// add info and oberve
[infos addObject:info];
// unlock prior to callout
pthread_mutex_unlock(&_lock);
[[ZQKVOSharedController sharedController] observe:object info:info];
}
#pragma mark API -
- (void)observe:(nullable id)object keyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options block:(KVOObserveBlk)block{
NSAssert(0 != keyPath.length && NULL != block, @"missing required parameters observe:%@ keyPath:%@ block:%p", object, keyPath, block);
if (nil == object || 0 == keyPath.length || NULL == block) {
return;
}
ZQKVOInfo *info = [[ZQKVOInfo alloc]initWithController:self keyPath:keyPath options:options block:block context:NULL];
[self observe:object info:info];
}
- (void)dealloc
{
pthread_mutex_lock(&_lock);
NSMapTable *objectInfoMaps = [_objectInfosMap copy];
// clear table and map
[_objectInfosMap removeAllObjects];
// unlock
pthread_mutex_unlock(&_lock);
ZQKVOSharedController *shareController = [ZQKVOSharedController sharedController];
for (id object in objectInfoMaps) {
// unobserve each registered object and infos
NSSet *infos = [objectInfoMaps objectForKey:object];
[shareController unobserve:object infos:infos];
}
pthread_mutex_destroy(&_lock);
}
應用:
- (void)viewDidLoad {
[super viewDidLoad];
_person = [[Person alloc]init];
_kvoController = [ZQKVOController controllerWithObserver:self];
[_kvoController observe:_person keyPath:@"age" options:NSKeyValueObservingOptionInitial|NSKeyValueObservingOptionNew block:^(NSString *keyPath, id observeObj, NSDictionary *valueChange) {
NSLog(@"keyPath:%@ age:%@",keyPath,valueChange[NSKeyValueChangeNewKey]);
}];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
_person.age++;
}