KVO在项目中使用很多,主要是两种原因会使KVO崩溃
- 1、KVO没有被移除
- 2、KVO移除的次数比添加的次数多
设计思路
- 1、利用
runtime
交换了addObserver:forKeyPath:options:context:
- 2、在替换的
addObserver:forKeyPath:options:context:
中
- a、创建一个
Map
,keyPath
作为key
,Value
是KVOItem
对象,而KVOItem
保存是的被监听的对象,监听的属性,这个Map
是保存利用关联属性保存的
- b、调用原生的
addObserver:forKeyPath:options:context:
方法,
- c、最后利用
method_setImplementation
修改了监听者的dealloc
方法的实现,里面先是判断是否被当作监听对象,如果有,遍历并移除KVO,然后调用原有的dealloc
方法 。没有使用交换方法的原因是,没有被当作监听的对象在dealloc方法中,也会判断被当作监听的对象。
- 3、但是在项目中,可能会有开发人员、系统自己在
dealloc
中移除KVO
,这样就会一个问题,因为在调用dealloc
之前, KVO
已经被移除了,这时候再次移除会崩溃,所以利用runtime
交换removeObserver:forKeyPath:
方法,由于removeObserver:forKeyPath:context
底层也是调用removeObserver:forKeyPath:
, 所以这个方法不用替换。
- 4、在替换的
removeObserver:forKeyPath:
中
- a、利用
observer
对象取出对应的Map
,判断是否存在对应的keyPath
, 如果存在,就移除KVO,并且从Map
移除对应的KeyPath
//
// NSObject+KVO.m
// CrashSafe
//
// Created by 无头骑士 GJ on 2019/1/26.
// Copyright ? 2019 无头骑士 GJ. All rights reserved.
//
#import "NSObject+KVO.h"
#import <objc/message.h>
static const char KVOArrayKey;
@interface WTKVOItem: NSObject
@property (nonatomic, weak) id obj;
@property (nonatomic, strong) NSString *keyPath;
@end
@implementation WTKVOItem
@end
@implementation NSObject (KVO)
+ (void)load
{
Method addObserver = class_getInstanceMethod(self, @selector(addObserver:forKeyPath:options:context:));
Method wt_addObserver = class_getInstanceMethod(self, @selector(wt_addObserver:forKeyPath:options:context:));
method_exchangeImplementations(addObserver, wt_addObserver);
Method removeObserver = class_getInstanceMethod(self, @selector(removeObserver:forKeyPath:));
Method wt_removeObserver = class_getInstanceMethod(self, @selector(wt_removeObserver:forKeyPath:));
method_exchangeImplementations(removeObserver, wt_removeObserver);
}
- (void)wt_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
if (observer == nil || keyPath == nil || keyPath.length == 0) return;
NSMutableDictionary *keyPathdict = objc_getAssociatedObject(observer, &KVOArrayKey);
if (keyPathdict == nil)
{
keyPathdict = [NSMutableDictionary dictionary];
objc_setAssociatedObject(observer, &KVOArrayKey, keyPathdict, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
WTKVOItem *item = [WTKVOItem new];
item.keyPath = keyPath;
item.obj = self;
keyPathdict[keyPath] = item;
[self wt_addObserver: observer forKeyPath: keyPath options: options context: context];
[self replaceImpl: observer.class];
}
- (void)wt_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
NSMutableDictionary *keyPaths = objc_getAssociatedObject(observer, &KVOArrayKey);
if (keyPaths == nil) return;
if ([keyPaths objectForKey: keyPath])
{
[self wt_removeObserver: observer forKeyPath: keyPath];
[keyPaths removeObjectForKey: keyPath];
}
}
- (void)wt_removeObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath context:(void *)context
{
NSMutableDictionary *keyPaths = objc_getAssociatedObject(observer, &KVOArrayKey);
if (keyPaths == nil) return;
if ([keyPaths objectForKey: keyPath])
{
[self wt_removeObserver: observer forKeyPath: keyPath context: context];
[keyPaths removeObjectForKey: keyPath];
}
}
- (void)replaceImpl:(Class)cls
{
Method dealloc = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
__block IMP deallocIMP = method_setImplementation(dealloc, imp_implementationWithBlock(^(__unsafe_unretained id self){
((void(*)(id, SEL))objc_msgSend)(self, @selector(cleanupSEL));
((void(*)(id, SEL))deallocIMP)(self, NSSelectorFromString(@"dealloc"));
}));
}
- (void)cleanupSEL
{
NSMutableDictionary *keyPaths = objc_getAssociatedObject(self, &KVOArrayKey);
if (keyPaths == nil) return;
[keyPaths enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull key, WTKVOItem * _Nonnull obj, BOOL * _Nonnull stop) {
[obj.obj removeObserver: self forKeyPath: obj.keyPath];
}];
objc_setAssociatedObject(self, &KVOArrayKey, NULL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end