KVO在項(xiàng)目中使用很多诈泼,主要是兩種原因會(huì)使KVO崩潰
- 1膝迎、KVO沒有被移除
- 2颈嚼、KVO移除的次數(shù)比添加的次數(shù)多
設(shè)計(jì)思路
- 1未玻、利用
runtime
交換了addObserver:forKeyPath:options:context:
- 2灾而、在替換的
addObserver:forKeyPath:options:context:
中
- a、創(chuàng)建一個(gè)
Map
扳剿,keyPath
作為key
旁趟,Value
是KVOItem
對(duì)象,而KVOItem
保存是的被監(jiān)聽的對(duì)象庇绽,監(jiān)聽的屬性锡搜,這個(gè)Map
是保存利用關(guān)聯(lián)屬性保存的
- b、調(diào)用原生的
addObserver:forKeyPath:options:context:
方法瞧掺,
- c耕餐、最后利用
method_setImplementation
修改了監(jiān)聽者的dealloc
方法的實(shí)現(xiàn),里面先是判斷是否被當(dāng)作監(jiān)聽對(duì)象辟狈,如果有肠缔,遍歷并移除KVO,然后調(diào)用原有的dealloc
方法 哼转。沒有使用交換方法的原因是明未,沒有被當(dāng)作監(jiān)聽的對(duì)象在dealloc方法中,也會(huì)判斷被當(dāng)作監(jiān)聽的對(duì)象壹蔓。
- 3趟妥、但是在項(xiàng)目中,可能會(huì)有開發(fā)人員佣蓉、系統(tǒng)自己在
dealloc
中移除KVO
披摄,這樣就會(huì)一個(gè)問題,因?yàn)樵谡{(diào)用dealloc
之前勇凭, KVO
已經(jīng)被移除了疚膊,這時(shí)候再次移除會(huì)崩潰,所以利用runtime
交換removeObserver:forKeyPath:
方法虾标,由于removeObserver:forKeyPath:context
底層也是調(diào)用removeObserver:forKeyPath:
, 所以這個(gè)方法不用替換酿联。
- 4、在替換的
removeObserver:forKeyPath:
中
- a、利用
observer
對(duì)象取出對(duì)應(yīng)的Map
贞让,判斷是否存在對(duì)應(yīng)的keyPath
, 如果存在周崭,就移除KVO,并且從Map
移除對(duì)應(yīng)的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
最后編輯于 :
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者