MVVM的KVO属性绑定自定义

て烟熏妆下的殇ゞ 提交于 2021-01-01 03:04:29

我们在使用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加了锁。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!