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。
简单实现思路是:
- 继承自NSObject,通过它的分类category给NSObject添加两个属性:一个用来存放keyPath和对应的block操作,另一个是和观察者同生命周期的一个控制器属性。
- 通过运行时,获取NSObject的两个属性,如果没有就创建。
- 在属性控制器的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,不会走第一个了:

如上只会走最后面那个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的使用
网友评论