iOS-設計一個在dealloc中自動移除KVO的分類
摘要:
KVO在專案中使用很多,主要是兩種原因會使KVO崩潰
1、KVO沒有被移除
2、KVO移除的次數比新增的次數多
設計思路
1、利用runtime
交換了addObserver:for...
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方法中,也會判斷被當作監聽的物件。
-
a、建立一個
-
3、但是在專案中,可能會有開發人員、系統自己在
dealloc
中移除KVO
,這樣就會一個問題,因為在呼叫dealloc
之前,KVO
已經被移除了,這時候再次移除會崩潰,所以利用runtime
交換removeObserver:forKeyPath:
方法,由於removeObserver:forKeyPath:context
底層也是呼叫removeObserver:forKeyPath:
, 所以這個方法不用替換。 -
4、在替換的
removeObserver:forKeyPath:
中-
a、利用
observer
物件取出對應的Map
,判斷是否存在對應的keyPath
, 如果存在,就移除KVO,並且從Map
移除對應的KeyPath
-
a、利用
// //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