我们在使用MVVM设计模式的时候会需要监听数据模型的属性变化,使用RAC是比较简洁好用的,但是如果不想引入RAC这样重量级的框架,那我们该如何编写符合监听需求的框架呢,我们的思路是封装系统的KVO,封装添加和删除监听的代码,改变的值通过block返回。
1、首先我们定义一个单例KVOController来统一管理属性监听,每个监听都有个回调。
typedef void(^ObserverKeyPathDidChange)(id value);
@interface KVOController : NSObject
+ (instancetype)shareInstance;
- (void)mj_observeObject:(NSObject *)object forKeyPath:(NSString *)keyPath observerKeyPathDidChange:(ObserverKeyPathDidChange)observerKeyPathDidChange;
@end
#import "KVOController.h"
#import <objc/runtime.h>
#import "MJObserver.h"
#import "NSObject+OBExtention.h"
@interface KVOController ()
@property (nonatomic,strong) NSMutableDictionary *cache;
@property (nonatomic,strong) dispatch_semaphore_t sema;
@end
@implementation KVOController
//单例初始化
+ (instancetype)shareInstance{
static KVOController *controller = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
controller = [KVOController new];
controller.cache = [NSMutableDictionary dictionary];
controller.sema = dispatch_semaphore_create(1);
});
return controller;
}
- (void)mj_observeObject:(NSObject *)object forKeyPath:(NSString *)keyPath observerKeyPathDidChange:(ObserverKeyPathDidChange)observerKeyPathDidChange{
dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
// NSLog(@"thread=%@,%@--%@",[NSThread currentThread],object,keyPath);
if (!object || !keyPath || [keyPath isEqualToString:@""] || !observerKeyPathDidChange) {
return;
}
NSDictionary *keyPathDic = [self.cache objectForKey:object];
if (keyPathDic) {
ObserverKeyPathDidChange change = keyPathDic[keyPath];
if (change) {
// NSLog(@"%@-%@已经存在",object,keyPath);
return;
}
}
[self mj_addObserverWithObject:object forKeyPath:keyPath observerKeyPathDidChange:observerKeyPathDidChange];
dispatch_semaphore_signal(_sema);
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
// NSLog(@"thread=%@,%@--%@",[NSThread currentThread],object,keyPath);
if (!change[@"new"]) {
return;
}
NSDictionary *keyPathDic = [self.cache objectForKey:[object description]];
if (!keyPathDic) {
return;
}
ObserverKeyPathDidChange block = [keyPathDic objectForKey:keyPath];
if (!block) {
return;
}
block(change[@"new"]);
dispatch_semaphore_signal(_sema);
}
- (void)mj_addObserverWithObject:(NSObject *)object forKeyPath:(NSString *)keyPath observerKeyPathDidChange:(ObserverKeyPathDidChange)observerKeyPathDidChange{
NSDictionary *keyPathDic = [self.cache objectForKey:[object description]];
[object addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL];
NSMutableDictionary *tempkeyPaths = [NSMutableDictionary dictionary];
if (keyPathDic) {
[tempkeyPaths addEntriesFromDictionary:keyPathDic];
}
[tempkeyPaths setObject:observerKeyPathDidChange forKey:keyPath];
MJObserver *observer = [[MJObserver alloc] init];
observer.object = object;
observer.name = [NSString stringWithFormat:@"%@-%@-tag",[object description],keyPath];
__weak KVOController *weakSelf = self;
observer.deallocBlock = ^(NSObject *removeObj) {
[weakSelf mj_removeObserver:removeObj];
};
[object.getObservers addObject:observer];
[self.cache setObject:tempkeyPaths forKey:[object description]];
}
- (void)mj_removeObserver:(NSObject *)removeObj{
dispatch_semaphore_wait(_sema, DISPATCH_TIME_FOREVER);
NSDictionary *keyPathDic = [self.cache objectForKey:[removeObj description]];
if (keyPathDic) {
for (NSString *keyPath in keyPathDic.allKeys) {
NSLog(@"removeObserver:object=%@--keyPath=%@",removeObj,keyPath);
[removeObj removeObserver:self forKeyPath:keyPath context:NULL];
}
[self.cache removeObjectForKey:[removeObj description]];
}
dispatch_semaphore_signal(_sema);
}
@end
2、KVOController内部使用一个cache 属性来保存被监听者以及监听的属性等信息,但是在我们在内部添加了监听,也要在被监听对象内存被释放的时候移除监听,因此,我们用分类NSObject+OBExtention为每个对象添加一个observers的数组,一个属性对应数组中的一个observer,当被监听对象释放,observer也会被释放,我们就可以通过回调deallocBlock来移除监听了。
MJObserver的实现如下:
#import <Foundation/Foundation.h>
typedef void(^DeallocBlock)(NSObject *removeObj);
@interface MJObserver : NSObject
@property (nonatomic,copy) DeallocBlock deallocBlock;
@property (nonatomic,assign) NSObject *object;
@property (nonatomic,copy) NSString *name;
@end
#import "MJObserver.h"
@implementation MJObserver
- (void)dealloc{
// NSLog(@"%@-dealloc",self.name);
if (self.deallocBlock) {
self.deallocBlock(self.object);
self.object = nil;
}
}
@end
分类NSObject+OBExtention的实现如下:
#import <Foundation/Foundation.h>
@interface NSObject (OBExtention)
- (NSHashTable *)getObservers;
@end
#import "NSObject+OBExtention.h"
#import <objc/runtime.h>
@implementation NSObject (OBExtention)
static char kObserverArrayKey;
- (NSHashTable *)getObservers{
NSHashTable *table = objc_getAssociatedObject(self,&kObserverArrayKey);
if (!table) {
table = [NSHashTable hashTableWithOptions:NSPointerFunctionsStrongMemory];
objc_setAssociatedObject(self, &kObserverArrayKey, table, OBJC_ASSOCIATION_RETAIN);
}
return table;
}
@end
3、为了让KVOController内部的cache访问更加安全,我直接用信号量sema加了锁。
来源:oschina
链接:https://my.oschina.net/jlongtian/blog/4871955