iOS-設(shè)計(jì)一個(gè)在dealloc中自動(dòng)移除KVO的分類

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旁趟,ValueKVOItem對(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)系作者
  • 序言:七十年代末喳张,一起剝皮案震驚了整個(gè)濱河市续镇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌销部,老刑警劉巖摸航,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異舅桩,居然都是意外死亡酱虎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門擂涛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來读串,“玉大人,你說我怎么就攤上這事撒妈』峙” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵狰右,是天一觀的道長杰捂。 經(jīng)常有香客問我,道長棋蚌,這世上最難降的妖魔是什么嫁佳? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮谷暮,結(jié)果婚禮上蒿往,老公的妹妹穿的比我還像新娘。我一直安慰自己坷备,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布情臭。 她就那樣靜靜地躺著省撑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪俯在。 梳的紋絲不亂的頭發(fā)上竟秫,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音跷乐,去河邊找鬼肥败。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的馒稍。 我是一名探鬼主播皿哨,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼纽谒!你這毒婦竟也來了证膨?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤鼓黔,失蹤者是張志新(化名)和其女友劉穎央勒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體澳化,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡崔步,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了缎谷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片井濒。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖慎陵,靈堂內(nèi)的尸體忽然破棺而出眼虱,到底是詐尸還是另有隱情,我是刑警寧澤席纽,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布捏悬,位于F島的核電站,受9級(jí)特大地震影響润梯,放射性物質(zhì)發(fā)生泄漏过牙。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一纺铭、第九天 我趴在偏房一處隱蔽的房頂上張望寇钉。 院中可真熱鬧,春花似錦舶赔、人聲如沸扫倡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽撵溃。三九已至,卻和暖如春锥累,著一層夾襖步出監(jiān)牢的瞬間缘挑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工桶略, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留语淘,地道東北人诲宇。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像惶翻,于是被迫代替她去往敵國和親姑蓝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容