美文网首页
KVO的封装

KVO的封装

作者: 简_爱SimpleLove | 来源:发表于2018-07-18 14:04 被阅读12次

KVO的一般用法

KVO 的使用与Notification非常相似,都能实现类与类之间一对多的通信。KVO是一个对象能够观察另外一个对象的属性的值,并且能够发现值的变化,适合任何类型的对象监听另外一个任意对象的属性的改变。比较常用来在Modal和View之间:View来监听Modal的变化而做出更改。

优点:
1.使用简单,只需三步完成;
2.当被观察者的对象的属性发生改变时,自动通知相应的观察者了;

缺点:
1.只能用来对对象的属性作出反应,而不会用来对方法或者动作作出反应;
2.观察的属性必须使用string来定义,编译器不会检测,容易出错;

KVO用于监听某个对象的属性,并且在属性发生改变时,很方便,下面是一般用法:

object : 被观察对象
observer: 观察对象
forKeyPath里面带上property的name,如UIView的frame、center等等
options: 有4个值,分别是:
NSKeyValueObservingOptionNew 把更改之后的值提供给处理方法
NSKeyValueObservingOptionOld 把更改之前的值提供给处理方法
NSKeyValueObservingOptionInitial 把初始化的值提供给处理方法,一旦注册,立马就会调用一次。通常它会带有新值,而不会带有旧值。
NSKeyValueObservingOptionPrior 分2次调用。在值改变之前和值改变之后。
context: 可以带入一些参数,其实这个挺好用的,任何类型都可以,自己强转就好了。

1、注册通知,添加观察者observer

[_person addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:@"Person"];
[_student addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:@"Student"];

2、实现观察者响应方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {

    id oldName = [change objectForKey:NSKeyValueChangeOldKey];
    id newName = [change objectForKey:NSKeyValueChangeNewKey];

    NSLog(@"--%@---%@--%@", oldName, newName, context);

    // context 用于进行区分被观察者,其实直接判断object就已经区分了被观察者
    if (context == @"Person") {
        NSLog(@"这是人");
    }

    if (context == @"Student") {
        NSLog(@"这是学生");
    }
}

3、移除观察者

- (void)dealloc {
    NSLog(@"dealloc");
    [_person removeObserver:self forKeyPath:@"name"];
    [_student removeObserver:self forKeyPath:@"name"];
}

KVO的封装代码

但是其实这样很不方便,因为需要在三个地方写代码,有时候可能会忘记,而且,如果有多个观察者,很有可能会忘写,和漏写,从而引发奔溃。为了方便使用,我们可以封装KVO。

简单实现思路是:

  1. 继承自NSObject,通过它的分类category给NSObject添加两个属性:一个用来存放keyPath和对应的block操作,另一个是和观察者同生命周期的一个控制器属性。
  2. 通过运行时,获取NSObject的两个属性,如果没有就创建。
  3. 在属性控制器的dealloc方法中,执行完所有释放观察者的操作。

代码如下:

NSObject+SJNewKVO.h文件

#import <Foundation/Foundation.h>

typedef void (^kvoBlock)(id newValue, id oldValue);

@interface NSObject (SJNewKVO)

/*
 object  被观察者,即观察对象
 keyPath 被观察的属性
 block   需要根据keyPath进行的操作
 */
- (void)sj_addObserved:(NSObject *)object keyPath:(NSString *)keyPath block:(kvoBlock)block;

@end

NSObject+SJNewKVO.m文件

#import "NSObject+SJNewKVO.h"
#import <objc/runtime.h>

/* 定义一个和观察者(即NSObject)同生命周期的控制器类 */

//当dealloc方法调用时,用于执行释放观察者对象的block
typedef void(^deallocBlock)(void);
@interface SJKVOController : NSObject

//被观察者,观察的对象
@property (nonatomic, strong) NSObject *observedObject;
//将所有需要移除观察者对象的所有block,放到一个数组里面
@property (nonatomic, strong) NSMutableArray <deallocBlock>*blockArr;

@end

@implementation SJKVOController

- (NSMutableArray<deallocBlock> *)blockArr {
    
    if (!_blockArr) {
        _blockArr = [NSMutableArray array];
    }
    return _blockArr;
}

- (void)dealloc {
    
    NSLog(@"SJKVOController dealloc");
    
    //遍历数组,依次让被观察者对象observedObject,移除observer观察者
    [_blockArr enumerateObjectsUsingBlock:^(deallocBlock  _Nonnull block, NSUInteger idx, BOOL * _Nonnull stop) {
        
        block();
    }];
}

@end

@interface NSObject ()

// 用于存储keyPath和对应的block操作
@property (nonatomic, strong) NSMutableDictionary <NSString *, kvoBlock>*dict;
// 添加一个和观察者(即NSObject)同生命周期的控制器属性
@property (nonatomic, strong) SJKVOController *kvoController;

@end

@implementation NSObject (SJNewKVO)

/*
 object  被观察者,即观察对象
 keyPath 被观察的属性
 block   需要根据keyPath进行的操作
 */
- (void)sj_addObserved:(NSObject *)object keyPath:(NSString *)keyPath block:(kvoBlock)block {
    
    // 将block和对应的keyPath 存储到字典中
    self.dict[keyPath] = block;
    
    /*
     将观察对象赋值给kvoController的被观察者属性
     self已经持有了kvoController,即观察者的生命周期和kvoController的生命周期一样了,当dealloc的时候,先走self的dealloc方法
     再走成员变量(kvoController)的dealloc方法
     */
    self.kvoController.observedObject = object;
    
    // __weak: weakSelf释放后会置为nil  __unsafe_unretained:weakSelf释放后不会置为nil,即变成了野指针(安全不?)
    __unsafe_unretained typeof (self)weakSelf = self;
//    __weak typeof (self)weakSelf = self;

    
    [self.kvoController.blockArr addObject:^{
        
        // 被观察者对象 移除 观察者
        [object removeObserver:weakSelf forKeyPath:keyPath];
    }];
    
    // 被观察者对象 添加 监听它的对象
    [object addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:NULL];
}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    id newValue = [change objectForKey:NSKeyValueChangeNewKey];
    id oldValue = [change objectForKey:NSKeyValueChangeOldKey];
    
    kvoBlock block = self.dict[keyPath];
    if (block) {
        block(newValue, oldValue);
    }
}


//  getter 和 setter 方法
- (NSMutableDictionary<NSString *,kvoBlock> *)dict {
    
    // 先取出NSObject的dict属性的字典,如果为空,就新创建一个
    NSMutableDictionary *tmpDict = objc_getAssociatedObject(self, @selector(dict));
    if (!tmpDict) {
        tmpDict = [NSMutableDictionary dictionary];
        
        // OBJC_ASSOCIATION_RETAIN_NONATOMIC 相当于 retain  强引用
        objc_setAssociatedObject(self, @selector(dict), tmpDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    return tmpDict;
  
}

- (SJKVOController *)kvoController {
    
    SJKVOController *tempKvoController = objc_getAssociatedObject(self, @selector(kvoController));
    if (!tempKvoController) {
        tempKvoController = [[SJKVOController alloc] init];
        objc_setAssociatedObject(self, @selector(kvoController), tempKvoController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return tempKvoController;
}

@end
注意:

但是上面封装的KVO,当同一个观察者对不同观察对象的同一个属性,进行观察时,只会走后添加的被观察者那个block,不会走第一个了:


image.png

如上只会走最后面那个block里面的内容,不会走第一个。

为了解决这个问题,我进行了进一步封装,进化版本,如下:

NSObject+SJNewKVO.h文件

#import <Foundation/Foundation.h>

typedef void (^kvoVoidBlock)(void);
typedef void (^kvoBlock)(id newValue, id oldValue);
typedef void (^kvoThreeParameterBlock)(id newValue, id oldValue, id object);


@interface NSObject (SJNewKVO)

/*
 object  被观察者,即观察对象
 keyPath 被观察的属性
 block   需要根据keyPath进行的操作
 */
- (void)sj_addObserved:(NSObject *)object keyPath:(NSString *)keyPath block:(kvoBlock)block;

- (void)sj_addObserved:(NSObject *)object keyPath:(NSString *)keyPath noParameterBlock:(kvoVoidBlock)block;

- (void)sj_addObserved:(NSObject *)object keyPath:(NSString *)keyPath threeParameterBlock:(kvoThreeParameterBlock)block;


@end

NSObject+SJNewKVO.m文件

#import "NSObject+SJNewKVO.h"
#import <objc/runtime.h>

/* 定义一个和观察者(即NSObject)同生命周期的控制器类 */

//当dealloc方法调用时,用于执行释放观察者对象的block
typedef void(^deallocBlock)(void);
@interface SJKVOController : NSObject

//被观察者,观察的对象
@property (nonatomic, strong) NSObject *observedObject;
//将所有需要移除观察者对象的所有block,放到一个数组里面
@property (nonatomic, strong) NSMutableArray <deallocBlock>*blockArr;

@end

@implementation SJKVOController

- (NSMutableArray<deallocBlock> *)blockArr {
    
    if (!_blockArr) {
        _blockArr = [NSMutableArray array];
    }
    return _blockArr;
}

- (void)dealloc {
    
    NSLog(@"SJKVOController dealloc");
    
    //遍历数组,依次让被观察者对象observedObject,移除observer观察者
    [_blockArr enumerateObjectsUsingBlock:^(deallocBlock  _Nonnull block, NSUInteger idx, BOOL * _Nonnull stop) {
        
        block();
    }];
}

@end

@interface NSObject ()

// 用于存储keyPath和对应的block操作
//@property (nonatomic, strong) NSMutableDictionary <NSString *, kvoBlock>*dict;
@property (nonatomic, strong) NSMutableDictionary *dict;

// 添加一个和观察者(即NSObject)同生命周期的控制器属性
@property (nonatomic, strong) SJKVOController *kvoController;

@end

// 根据观察者,被观察者,keyPath拼成唯一的字典的key
#define KEYPATH(a, b, c) [NSString stringWithFormat:@"%@-%@-%@",[a class], [b class], c]
#define KEYPATH_VOID(a, b, c) [NSString stringWithFormat:@"%@-%@-%@",[a class], [b class], c]
#define KEYPATH_THREPARAMETER(a, b, c) [NSString stringWithFormat:@"%@-%@-%@",[a class], [b class], c]

#define KVOBLOCK @"kvoBlock"
#define KVOVOIDBLOCK @"kvoVoidBlock"
#define KVOTHREEPARAMETERBLOCK @"kvoThreeParameterBlock"


@implementation NSObject (SJNewKVO)

/*
 object  被观察者,即观察对象
 keyPath 被观察的属性
 block   需要根据keyPath进行的操作
 */
- (void)sj_addObserved:(NSObject *)object keyPath:(NSString *)keyPath block:(kvoBlock)block {
    
    [self sub_addObserved:object keyPath:keyPath context:KVOBLOCK block:block];
}

- (void)sj_addObserved:(NSObject *)object keyPath:(NSString *)keyPath noParameterBlock:(kvoVoidBlock)block {
    
    [self sub_addObserved:object keyPath:keyPath context:KVOVOIDBLOCK block:block];
}

- (void)sj_addObserved:(NSObject *)object keyPath:(NSString *)keyPath threeParameterBlock:(kvoThreeParameterBlock)block {
    
    [self sub_addObserved:object keyPath:keyPath context:KVOTHREEPARAMETERBLOCK block:block];
}


- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
    
    id newValue = [change objectForKey:NSKeyValueChangeNewKey];
    id oldValue = [change objectForKey:NSKeyValueChangeOldKey];

    if (context == KVOBLOCK) {
        
        kvoBlock block = self.dict[KEYPATH(self, object, keyPath)];
        if (block) {
            block(newValue, oldValue);
        }
        
    } else if (context == KVOVOIDBLOCK)  {
        
        kvoVoidBlock voidBlock = self.dict[KEYPATH_VOID(self, object, keyPath)];
        if (voidBlock) {
            voidBlock();
        }
        
    } else if (context == KVOTHREEPARAMETERBLOCK)  {
        
        kvoThreeParameterBlock threeParameterblock = self.dict[KEYPATH_THREPARAMETER(self, object, keyPath)];
        if (threeParameterblock) {
            threeParameterblock(newValue, oldValue, object);
        }
    }

}


// block中的操作
- (void)sub_addObserved:(NSObject *)object keyPath:(NSString *)keyPath context:(void *)context block:(id)block {
    
    // 将block和对应的keyPath 存储到字典中
    if (context == KVOBLOCK) {
        
        self.dict[KEYPATH(self, object, keyPath)] = block;
        
    } else if (context == KVOVOIDBLOCK)  {
        
        self.dict[KEYPATH_VOID(self, object, keyPath)] = block;

    } else if (context == KVOTHREEPARAMETERBLOCK)  {
        
        self.dict[KEYPATH_THREPARAMETER(self, object, keyPath)] = block;

    }
    
    /*
     将观察对象赋值给kvoController的被观察者属性
     self已经持有了kvoController,即观察者的生命周期和kvoController的生命周期一样了,当dealloc的时候,先走self的dealloc方法
     再走成员变量(kvoController)的dealloc方法
     */
    self.kvoController.observedObject = object;
    
    // __weak: weakSelf释放后会置为nil  __unsafe_unretained:weakSelf释放后不会置为nil,即变成了野指针(安全不?)
    __unsafe_unretained typeof (self)weakSelf = self;
    //    __weak typeof (self)weakSelf = self;
    
    
    [self.kvoController.blockArr addObject:^{
        
        // 被观察者对象 移除 观察者
        [object removeObserver:weakSelf forKeyPath:keyPath];
    }];
    
    // 被观察者对象 添加 监听它的对象
    [object addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld context:context];
}


//  getter 和 setter 方法
- (NSMutableDictionary<NSString *,kvoBlock> *)dict {
    
    // 先取出NSObject的dict属性的字典,如果为空,就新创建一个
    NSMutableDictionary *tmpDict = objc_getAssociatedObject(self, @selector(dict));
    if (!tmpDict) {
        tmpDict = [NSMutableDictionary dictionary];
        
        // OBJC_ASSOCIATION_RETAIN_NONATOMIC 相当于 retain  强引用
        objc_setAssociatedObject(self, @selector(dict), tmpDict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    
    return tmpDict;
  
}

- (SJKVOController *)kvoController {
    
    SJKVOController *tempKvoController = objc_getAssociatedObject(self, @selector(kvoController));
    if (!tempKvoController) {
        tempKvoController = [[SJKVOController alloc] init];
        objc_setAssociatedObject(self, @selector(kvoController), tempKvoController, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    return tempKvoController;
}

@end

中间查阅的资料:
iOS中Block的用法,举例,解析与底层原理(这可能是最详细的Block解析)
OC中id和void*的区别
ios KVO的使用

相关文章

网友评论

      本文标题:KVO的封装

      本文链接:https://www.haomeiwen.com/subject/shkopftx.html