观察者模式-KVO详解
志愿不像通知机制那样通过一个通知中心通知所有观察者对象,而是在对象属性变化时通知会被直接发送给观察者对象.KVO机制解析图:
KVO(键值观察)
KVO(键值观察)是Objective-C对观察者模式(Observer Pattern)的实现。也是Cocoa Binding的基础。可以用于监听某个对象属性值的改变,当被观察对象的某个属性发生更改时,观察者对象会获得通知。
KVO(Key-Value Observing)
二话不说,直接撸起袖子就是干。
#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
然后我们进行对个人属性进行监听,看看监听前后的打印变化:
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
主要参考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,/** 监听 */
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<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) {
/** 移除相同路径的监听 */
[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++;
}