观察者模式-KVO详解

KVO不像通知机制那样通过一个通知中心通知所有观察者对象,而是在对象属性变化时通知会被直接发送给观察者对象.KVO机制解析图:
观察者模式-KVO详解
文章图片
屏幕快照 2018-08-23 上午10.10.50.png KVO(Key-Value Observing) KVO(Key-Value Observing) 是 Objective-C 对观察者模式(Observer Pattern)的实现。也是 Cocoa Binding 的基础。当被观察对象的某个属性发生更改时,观察者对象会获得通知。
KVO内部实现原理 二话不说,直接撸起袖子就是干。

#import "Person.h" #import @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属性进行监听,看看监听前后的打印变化:
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之后,系统通过runtime动态的创建一个派生类NSKVONotifying_Person,而根据class_getSuperclass得到的结果竟然是Person,然后age是使我们KVO需要观察的属性,它的setter函数指针变了。而我们也知道,所谓的OC的消息机制是通过isa去查找实现的,那么我们可以得到一下结论:
  • KVO是基于runtime机制实现的
  • 当某个类的属性对象第一次被观察时,系统就会在运行期动态地创建该类的一个派生类,在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制
  • 如果原类为Person,那么生成的派生类名为NSKVONotifying_Person
  • 每个类对象中都有一个isa指针指向当前类,当一个类对象的第一次被观察,那么系统会偷偷将isa指针指向动态生成的派生类,从而在给被监控属性赋值时执行的是派生类的setter方法
  • 键值观察通知依赖于NSObject 的两个方法: willChangeValueForKey:didChangevlueForKey:;在一个被观察属性发生改变之前, willChangeValueForKey:一定会被调用,这就 会记录旧的值。而当改变发生后,didChangeValueForKey:会被调用,继而 observeValueForKey:ofObject:change:context: 也会被调用。
  • 补充:KVO的这套实现机制中苹果还偷偷重写了class方法,让我们误认为还是使用的当前类,从而达到隐藏生成的派生类。
观察者模式-KVO详解
文章图片
1429890-b28e010d3a7dbdb8.png 自定义KVO 主要参考KVOController
#import 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 #pragma mark Utilities - typedef NS_ENUM(uint8_t, ZQKVOInfoState) { ZQKVOInfoStateInitial = 0,/** 初始化 */ ZQKVOInfoStateObserving,/** 监听 */ ZQKVOInfoStateNotObserving,/** 为监听 */ }; 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; /** 添加监听 */ - (void)observe:(id)object info:(nullable ZQKVOInfo *)info; /** 取消监听 */ - (void)unobserve:(id)object infos:(nullable NSSet *)infos; @end@implementation ZQKVOSharedController { NSHashTable *_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) { /** 移除相同路径的监听 */ [object removeObserver:self forKeyPath:info->_keyPath context:(void *)info]; } }- (void)unobserve:(id)object infos:(nullable NSSet *)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 *)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 *> *_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 *)touches withEvent:(UIEvent *)event{ _person.age++; }

【观察者模式-KVO详解】下一篇:KVC详解

    推荐阅读