------更新------:
之前沒有判斷observer是否一致亡呵,有個(gè)別情況會(huì)無法處理哥攘,所以更新添加了observer判斷
一、使用場景
有時(shí)候我們會(huì)忘記添加多次KVO監(jiān)聽或者诚些,不小心刪除如果KVO監(jiān)聽男韧,如果添加多次KVO監(jiān)聽這個(gè)時(shí)候我們就會(huì)接受到多次監(jiān)聽。
如果刪除多次kvo程序就會(huì)造成catch葛圃,如下圖
這時(shí)候我們就可以想一些方案來防止這種情況的發(fā)生千扔。
二、使用技術(shù)
核心 : 利用runtime實(shí)現(xiàn)方法交換装悲,進(jìn)行攔截add和remove進(jìn)行操作昏鹃。
- 方案一 :利用 @try @catch
- 方案二 :利用 模型數(shù)組 進(jìn)行存儲(chǔ)記錄
- 方案二 :利用 observationInfo 里私有屬性
(1) 方案一(只能針對刪除多次KVO的情況下)
利用 @try @catc
不得不說這種方法真是很Low,但是很簡單就可以實(shí)現(xiàn)诀诊。
這種方法只能針對多次刪除KVO的處理洞渤,原理就是try catch可以捕獲異常,不讓程序catch属瓣。這樣就實(shí)現(xiàn)了防止多次刪除KVO载迄。
@try {
[self.btn removeObserver:self forKeyPath:@"kkl"];
}
@catch (NSException *exception) {
NSLog(@"多次刪除了");
}
普通情況下,使用這種方法就需要每次removeObserver的時(shí)候抡蛙,就加上去一個(gè)@try @catch
有個(gè)簡單的方法:給NSObject 增加一個(gè)分類护昧,然后利用Run time 交換系統(tǒng)的 removeObserver方法,在里面添加 @try @catch粗截。
runtime 就不多說了惋耙,大家自己自己查下相關(guān)資料有很多。
下面就直接上實(shí)現(xiàn)代碼了:
NSObject+DSKVO.m
#import "NSObject+DSKVO.h"
#import <objc/runtime.h>
@implementation NSObject (DSKVO)
+ (void)load
{
[self switchMethod];
}
// 交換后的方法
- (void)removeDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
@try {
[self removeDasen:observer forKeyPath:keyPath];
} @catch (NSException *exception) {}
}
+ (void)switchMethod
{
SEL removeSel = @selector(removeObserver:forKeyPath:);
SEL myRemoveSel = @selector(removeDasen:forKeyPath:);
Method systemRemoveMethod = class_getClassMethod([self class],removeSel);
Method DasenRemoveMethod = class_getClassMethod([self class], myRemoveSel);
method_exchangeImplementations(systemRemoveMethod, DasenRemoveMethod);
}
@end
(2) 方案二
利用 模型數(shù)組 進(jìn)行存儲(chǔ)記錄
第一步 利用交換方法,攔截到需要的東西
1绽榛,是在監(jiān)聽哪個(gè)對象湿酸。
2,是在監(jiān)聽的keyPath是什么灭美。
第二步 存儲(chǔ)思路
1推溃,我們需要一個(gè)模型用來存儲(chǔ)
哪個(gè)對象執(zhí)行了addObserver、監(jiān)聽的KeyPath是什么届腐。
2铁坎,我們需要一個(gè)數(shù)組來存儲(chǔ)這個(gè)模型。
第三步 進(jìn)行存儲(chǔ)
1犁苏,利用runtime 攔截到對象和keyPath,創(chuàng)建模型然后進(jìn)行賦值模型相應(yīng)的屬性硬萍。
2,然后存儲(chǔ)進(jìn)數(shù)組中去傀顾。
第三步 存儲(chǔ)之前的檢索處理
1襟铭,在存儲(chǔ)之前,為了防止多次addObserver相同的屬性短曾,這個(gè)時(shí)候我們就可以寒砖,遍歷數(shù)組,取出每個(gè)一個(gè)模型嫉拐,然后取出模型中的對象哩都,首先判斷對象是否一致,然后判斷keypath是否一致2婉徘,對于添加KVO監(jiān)聽:如果不一致那么就執(zhí)行利用交換后方法執(zhí)行addObserver方法漠嵌。
3,對于刪除KVO監(jiān)聽: 如果一致那么我們就執(zhí)行刪除監(jiān)聽,否則不執(zhí)行盖呼。
4儒鹿,上代碼了:
NSObject+DSKVO.m
#import "NSObject+DSKVO.h"
#import "DSObserver.h"
#import "ObserverData.h"
#import <objc/runtime.h>
@implementation NSObject (DSKVO)
+ (void)load
{
[self switchMethod];
}
// 交換后的方法
- (void)removeDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
NSMutableArray *Observers = [DSObserver sharedDSObserver];
ObserverData *userPathData = [self observerKeyPath:keyPath];
// 如果有該key值那么進(jìn)行刪除
if (userPathData) {
[Observers removeObject:userPathData];
[self removeDasen:observer forKeyPath:keyPath];
}
return;
}
// 交換后的方法
- (void)addDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
ObserverData *userPathData= [[ObserverData alloc]initWithObjc:self key:keyPath];
NSMutableArray *Observers = [DSObserver sharedDSObserver];
// 如果沒有注冊,那么才進(jìn)行注冊
if (![self observerKeyPath:keyPath]) {
[Observers addObject:userPathData];
[self addDasen:observer forKeyPath:keyPath options:options context:context];
}
}
// 進(jìn)行檢索几晤,判斷是否已經(jīng)存儲(chǔ)了該Key值
- (ObserverData *)observerKeyPath:(NSString *)keyPath
{
NSMutableArray *Observers = [DSObserver sharedDSObserver];
for (ObserverData *data in Observers) {
if ([data.objc isEqual:self] && [data.keyPath isEqualToString:keyPath]) {
return data;
}
}
return nil;
}
+ (void)switchMethod
{
SEL removeSel = @selector(removeObserver:forKeyPath:);
SEL myRemoveSel = @selector(removeDasen:forKeyPath:);
SEL addSel = @selector(addObserver:forKeyPath:options:context:);
SEL myaddSel = @selector(addDasen:forKeyPath:options:context:);
Method systemRemoveMethod = class_getClassMethod([self class],removeSel);
Method DasenRemoveMethod = class_getClassMethod([self class], myRemoveSel);
Method systemAddMethod = class_getClassMethod([self class],addSel);
Method DasenAddMethod = class_getClassMethod([self class], myaddSel);
method_exchangeImplementations(systemRemoveMethod, DasenRemoveMethod);
method_exchangeImplementations(systemAddMethod, DasenAddMethod);
}
ObserverData 模型類文件有兩個(gè)屬性
@property (nonatomic, strong)id objc;
@property (nonatomic, copy) NSString *keyPath;
DSObserver 類是一個(gè)單例數(shù)組
@implementation DSObserver
+ (instancetype)sharedDSObserver
{
static id objc;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
objc = [NSMutableArray array];
});
return objc;
}
@end
(3) 方案三
利用 observationInfo 里私有屬性
第一步 簡單介紹下observationInfo屬性
1约炎,只要是繼承與NSObject的對象都有observationInfo屬性.
2,observationInfo是系統(tǒng)通過分類給NSObject增加的屬性蟹瘾。
3圾浅,分類文件是NSKeyValueObserving.h這個(gè)文件
4,這個(gè)屬性中存儲(chǔ)有屬性的監(jiān)聽者憾朴,通知者狸捕,還有監(jiān)聽的keyPath,等等KVO相關(guān)的屬性众雷。
5灸拍,observationInfo是一個(gè)void指針做祝,指向一個(gè)包含所有觀察者的一個(gè)標(biāo)識信息對象,信息包含了每個(gè)監(jiān)聽的觀察者,注冊時(shí)設(shè)定的選項(xiàng)等株搔。
@property (nullable) void *observationInfo;
6剖淀,observationInfo結(jié)構(gòu) (箭頭所指是我們等下需要用到的地方)
第二步 實(shí)現(xiàn)方案思路
1,通過私有屬性直接拿到當(dāng)前對象所監(jiān)聽的keyPath纤房,和observer
2,判斷keyPath是否有無翻诉,和observer是否對應(yīng)一直炮姨,來實(shí)現(xiàn)防止多次重復(fù)添加和刪除KVO監(jiān)聽。
3碰煌,通過Dump Foundation.framework 的頭文件舒岸,和直接xcode查看observationInfo的結(jié)構(gòu),發(fā)現(xiàn)有一個(gè)數(shù)組用來存儲(chǔ)NSKeyValueObservance對象芦圾,經(jīng)過測試和調(diào)試蛾派,發(fā)現(xiàn)這個(gè)數(shù)組存儲(chǔ)的需要監(jiān)聽的對象中,監(jiān)聽了幾個(gè)屬性个少,如果監(jiān)聽兩個(gè)洪乍,數(shù)組中就是2個(gè)對象。
比如這是監(jiān)聽兩個(gè)屬性狀態(tài)下的數(shù)組
4夜焦,NSKeyValueObservance屬性簡單說明
_observer屬性:里面放的是監(jiān)聽屬性的通知這壳澳,也就是當(dāng)屬性改變的時(shí)候讓哪個(gè)對象執(zhí)行observeValueForKeyPath的對象。
_property 里面的NSKeyValueProperty NSKeyValueProperty存儲(chǔ)的有keyPath,其他屬性我們用不到茫经,暫時(shí)就不說了巷波。
5,拿出keyPath
這時(shí)候思路就有了卸伞,首先拿出_observances數(shù)組抹镊,然后遍歷拿出里面_property對象里面的NSKeyValueProperty下的一個(gè)keyPath,然后進(jìn)行判斷需要?jiǎng)h除或添加的keyPath是否一致荤傲,然后再次判斷傳遞過來的observer和監(jiān)聽的是否一致垮耳,然后分別進(jìn)行處理就行了。
6弃酌,上代碼
#import "NSObject+DSKVO.h"
#import <objc/runtime.h>
@implementation NSObject (DSKVO)
+ (void)load
{
[self switchMethod];
}
// 交換后的方法
- (void)removeDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath
{
if ([self observerKeyPath:keyPath observer:observer]) {
[self removeDasen:observer forKeyPath:keyPath];
}
}
// 交換后的方法
- (void)addDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
if (![self observerKeyPath:keyPath observer:observer]) {
[self addDasen:observer forKeyPath:keyPath options:options context:context];
}
}
// 進(jìn)行檢索獲取Key
- (BOOL)observerKeyPath:(NSString *)key observer:(id )observer
{
id info = self.observationInfo;
NSArray *array = [info valueForKey:@"_observances"];
for (id objc in array) {
id Properties = [objc valueForKeyPath:@"_property"];
id newObserver = [objc valueForKeyPath:@"_observer"];
NSString *keyPath = [Properties valueForKeyPath:@"_keyPath"];
if ([key isEqualToString:keyPath] && [newObserver isEqual:observer]) {
return YES;
}
}
return NO;
}
+ (void)switchMethod
{
SEL removeSel = @selector(removeObserver:forKeyPath:);
SEL myRemoveSel = @selector(removeDasen:forKeyPath:);
SEL addSel = @selector(addObserver:forKeyPath:options:context:);
SEL myaddSel = @selector(addDasen:forKeyPath:options:context:);
Method systemRemoveMethod = class_getClassMethod([self class],removeSel);
Method DasenRemoveMethod = class_getClassMethod([self class], myRemoveSel);
Method systemAddMethod = class_getClassMethod([self class],addSel);
Method DasenAddMethod = class_getClassMethod([self class], myaddSel);
method_exchangeImplementations(systemRemoveMethod, DasenRemoveMethod);
method_exchangeImplementations(systemAddMethod, DasenAddMethod);
}
參考文章:http://www.bkjia.com/IOSjc/993206.html
參考人員:tyh
github地址:https://github.com/DaSens/DSKVO
感謝各位閱讀氨菇,有什么補(bǔ)充的希望大家提出來。