1 KVO基础
KVO是用来做监听的,这个相信大多数人都知道,常规的代码如下:
[self.view addObserver:self
forKeyPath:@"isSelected"
options:NSKeyValueObservingOptionNew
context:nil];
四个参数:
调用该方法的对象就是要监听的对象,即被监听的对象。
addOberver的参数就是监听者对象,即监听回调接收者。
KeyPath的参数就是要监听的健值。
context的参数这个其实就是传值参数,这里传什么值,监听回调方法的context就带过来什么值,可以用来传一些特征参数什么的,但是这个参数传进来并不会被强引用,如果传对象进来要注意让对象的在监听接收的时候没有被释放掉。
KVO到底干了什么:
2 KVO到底监听了什么
很多时候KVO都被用来监听属性,其实KVO并不是只能监听属性,只要KVC能够调用的KVO都能够监听。比如就算被监听没有 touch 属性, 也没有touch成员变量,但是如果对象中有setTouch/touch方法,那么KVO也会被被响应。
例如如下代码,对象并没有touch属性,但是有touch的get/set方法,如果调用了setTouch方法,那么KVO就会监听到,并通过touch方法返回值。
️:没有属性对应的情况下如果没有touch方法,KVO找不到值会崩溃。
事实上KVC也是一样,其取值和赋值也并不是直接取找对象的属性和成员变量,而是通过先找set/get方法 => 没找到再找变量 的顺序调用的。
[self.rootView jkr_addObserver:self forKeyPath:@"touch" change:^(id newValue) {
NSLog(@"%@", newValue);
}];
...
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.isSelected = !self.isSelected;
if (self.isSelected) {
self.circleColor = [UIColor redColor];
[self setNeedsDisplay];
} else {
self.circleColor = [UIColor orangeColor];
[self setNeedsDisplay];
}
[self setTouch:YES];
}
#pragma mark - KVO测试
- (void)setTouch:(BOOL)touch {
}
- (BOOL)touch {
return self.isSelected;
}
3 简单的实现KVO封装
如上的代码,我是通过block直接捕捉KVO的调用,就是一个KVO的封装。KVO默认的代码实现起来有一些问题:
1:添加和监听回调代码是分开的,很零散
2:回调的方法都在一个方法中,要自己通过keyPath区别
3:要自己手动移除监听,如果忘记移除还会报错
这个是我自己通过runtime相关方法来实现的一个KVO封装,网上也有很多KVO的封装,但是都是要新建一个自定义对象数组,通过自定义对象来保存监听者、被监听者、回调block来实现的。我想了些办法,通过runtime给监听和被监听对象动态添加了对应的属性来保存这些信息,不需要新建自定义对象。
NSObject+JKR_Observer.h
#import <Foundation/Foundation.h>
#import <objc/runtime.h>
typedef void (^changeBlock)(id newValue);
@interface NSObject (JKR_Observer)
- (void)jkr_addObserver:(NSObject *)object forKeyPath:(NSString *)keyPath change:(changeBlock)change;
@end
NSObject+JKR_Observer.m
#import "NSObject+JKR_Observer.h"
@interface NSObject ()
@property NSMutableDictionary<NSString *, changeBlock> *jkr_observer_blocks;
@property NSMutableArray<NSString *> *jkr_observer_keyPaths;
@property NSObject *jkr_observer_observerdObject;
@end
@implementation NSObject (JKR_Observer)
- (void)jkr_addObserver:(NSObject *)object forKeyPath:(NSString *)keyPath change:(changeBlock)change {
NSLog(@"%@ 添加监听者 %@ 被监听的值:%@", self, object, keyPath);
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
method_exchangeImplementations(class_getInstanceMethod([object class], NSSelectorFromString(@"dealloc")), class_getInstanceMethod([self class], @selector(jkr_dealloc)));
});
[self addObserver:object forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:(__bridge void * _Nullable)([NSString stringWithFormat:@"%zd", self.jkr_observer_blocks.count])];
[self.jkr_observer_blocks setObject:change forKey:[NSString stringWithFormat:@"%zd", self.jkr_observer_blocks.count]];
NSMutableArray *keyPaths = [object valueForKey:@"jkr_observer_keyPaths"];
[keyPaths addObject:keyPath];
object.jkr_observer_observerdObject = self;
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
NSLog(@"监听者:%@ 监听到被监听者 %@ 的值 %@ 改变", self, object, keyPath);
NSString *key = (__bridge NSString *)(context);
NSMutableDictionary *dict = [object valueForKey:@"jkr_observer_blocks"];
if (!dict) return;
changeBlock block = [dict valueForKey:key];
if (block) {
id newValue = [change valueForKey:NSKeyValueChangeNewKey];
block(newValue);
}
}
- (void)setJkr_observer_blocks:(NSMutableDictionary<NSString *,changeBlock> *)jkr_observer_blocks {
objc_setAssociatedObject(self, "jkr_observer_blocks", jkr_observer_blocks, OBJC_ASSOCIATION_RETAIN);
}
- (NSMutableDictionary *)jkr_observer_blocks {
NSMutableDictionary *blocks = objc_getAssociatedObject(self, "jkr_observer_blocks");
if (!blocks) {
blocks = [NSMutableDictionary dictionary];
objc_setAssociatedObject(self, "jkr_observer_blocks", blocks, OBJC_ASSOCIATION_RETAIN);
}
return blocks;
}
- (void)setJkr_observer_keyPaths:(NSMutableArray<NSString *> *)jkr_observer_keyPaths {
objc_setAssociatedObject(self, @"jkr_observer_keyPaths", jkr_observer_keyPaths, OBJC_ASSOCIATION_RETAIN);
}
- (NSMutableArray<NSString *> *)jkr_observer_keyPaths {
NSMutableArray *keyPaths = objc_getAssociatedObject(self, @"jkr_observer_keyPaths");
if (!keyPaths) {
keyPaths = [NSMutableArray array];
objc_setAssociatedObject(self, @"jkr_observer_keyPaths", keyPaths, OBJC_ASSOCIATION_RETAIN);
}
return keyPaths;
}
- (NSObject *)jkr_observer_observerdObject {
return objc_getAssociatedObject(self, @"jkr_observer_observerdObject");
}
- (void)setJkr_observer_observerdObject:(NSObject *)jkr_observer_observerdObject {
objc_setAssociatedObject(self, @"jkr_observer_observerdObject", jkr_observer_observerdObject, OBJC_ASSOCIATION_ASSIGN);
}
- (void)jkr_dealloc {
NSMutableArray *paths = self.jkr_observer_keyPaths;
if (paths.count) {
[paths enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"被监听者 %@ 移除监听者 %@ 被移除的监听值 %@", self.jkr_observer_observerdObject, self, obj);
[self.jkr_observer_observerdObject removeObserver:self forKeyPath:paths[idx]];
}];
}
NSLog(@"dealloc");
[self jkr_dealloc];
}
@end