iOS攔截系統(tǒng)KVO監(jiān)聽(tīng),防止多次刪除和添加【it is not registered as an observer.】

淺談
  • 最近項(xiàng)目中處理kvo 的時(shí)候,遇到一個(gè)問(wèn)題:當(dāng)我操作的時(shí)候,會(huì)發(fā)現(xiàn)kvo 釋放的時(shí)候,會(huì)崩潰, 崩潰日志如下

Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observerfor the key path "kvoState" frombecause it is not registered as an observer.'

經(jīng)過(guò)反復(fù)研究,發(fā)現(xiàn)了錯(cuò)誤的原因,并且找到解決錯(cuò)誤的辦法下面我將介紹一下我的思路:(慢慢來(lái) 跟著我的思路走)

  • 1、我在AppDelegate里面添加一個(gè)屬性
//測(cè)試kvo設(shè)置的一個(gè)字段 
@property(nonatomic,copy)NSString *kvoState;
  • 2、我在我創(chuàng)建的一個(gè)ViewController(SecondViewController)里面去監(jiān)聽(tīng)這個(gè)屬性,但是沒(méi)有 調(diào)用monitorNet 方法
- (void)monitorNet {
    
    AppDelegate *appDelegate = (AppDelegate *)[UIApplication  sharedApplication].delegate;    // kvo監(jiān)聽(tīng)屬性值的改變
    [appDelegate addObserver:self forKeyPath:@"kvoState" options:NSKeyValueObservingOptionNew context:nil];
}

/**
 *  KVO 監(jiān)聽(tīng)方法
 *
 *  @param keyPath 監(jiān)聽(tīng)的屬性名稱
 *  @param object 被監(jiān)聽(tīng)的對(duì)象
 *  @param change 屬性的值
 *  @param context 添加監(jiān)聽(tīng)時(shí)傳來(lái)的值
 */
- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(id)object
                        change:(NSDictionary *)change
                       context:(void *)context  {
    
    AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    
    if ([keyPath isEqualToString:@"kvoState"]) {
        
        NSNumber *number = [change objectForKey:@"new"];
        
        NSInteger item = [number integerValue];
        
        NSLog(@"%@====",appDelegate.kvoState);
        
        NSLog(@"%@----",number);
        
        if ([object isKindOfClass:[AppDelegate class]] ) {
            
        }
        
    }
    
}

  • 3、然后我再去釋放 復(fù)寫(xiě)系統(tǒng) dealloc 這個(gè)方法
-(void)dealloc {
    NSLog(@"銷毀了");
    AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    
    [appDelegate removeObserver:self forKeyPath:@"kvoState"];
    
    //或者多次調(diào)用
    [appDelegate removeObserver:self forKeyPath:@"kvoState"];

}
  • 4、在第二步之后,我點(diǎn)擊一個(gè)button ,push 到 另外一個(gè)ViewController(TestViewController)里面,然后在TestViewController里面,點(diǎn)擊button ,在這個(gè)button 的點(diǎn)擊事件里面去執(zhí)行下面的代碼:(特地演示錯(cuò)誤)
- (IBAction)btnAction:(id)sender {
    
    SecondViewController *vc = [[SecondViewController alloc] init];
    [self.navigationController pushViewController:vc  animated:YES];
}

當(dāng)這個(gè)方法執(zhí)行完之后,就會(huì)出現(xiàn)前面所展示的錯(cuò)誤

*** Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observer <SecondViewController 0x7f8b7ef0bad0> for the key path "kvoState" from <AppDelegate 0x600002c8b020> because it is not registered as an observer.'
*** First throw call stack:

為什么會(huì)出現(xiàn)這種錯(cuò)誤呢????其實(shí)出現(xiàn)這種錯(cuò)誤也很簡(jiǎn)單的:
首先在buttonAction 這個(gè)方法內(nèi),secondVC 他是一個(gè)局部變量,現(xiàn)在是ARC 管理,當(dāng)這個(gè)方法執(zhí)行完成以后,會(huì)銷毀 secondVC 這個(gè)對(duì)象,那么,很自然的就會(huì)調(diào)用 SecondViewController 里面的 dealloc 這個(gè)方法【也就是第三步的方法,請(qǐng)看第三步】

解釋:

根據(jù)錯(cuò)誤提示,appDelegate 的屬性kvoState 會(huì)被remove,但是的這個(gè)時(shí)候,it is not registered as an observer,所以,就會(huì)出現(xiàn)上述的崩潰現(xiàn)象說(shuō)了這么多,大家能理解這個(gè)崩潰的原因了嗎?(PS:不懂的話也請(qǐng)繼續(xù)了解下面的內(nèi)容)

總之就是:有時(shí)候我們會(huì)忘記添加多次KVO監(jiān)聽(tīng)或者,不小心刪除如果KVO監(jiān)聽(tīng),如果添加多次KVO監(jiān)聽(tīng)這個(gè)時(shí)候我們就會(huì)接受到多次監(jiān)聽(tīng)。如果刪除多次kvo程序就會(huì)造成catch既然問(wèn)題的出現(xiàn),那么,肯定會(huì)伴隨著事務(wù)的解決

下面我講給大家講解幾個(gè)解決的方法(百度查資料的,親自驗(yàn)證,安全可靠),

方案有三種:

那么iOS開(kāi)發(fā)-黑科技防止多次添加刪除KVO出現(xiàn)的問(wèn)題

  • 方案一 :利用 @try @catch

  • 利用 @try @catch(只能針對(duì)刪除多次KVO的情況下)

    利用 @try @catc 不得不說(shuō)這種方法真是很Low,不過(guò)很簡(jiǎn)單就可以實(shí)現(xiàn)。(對(duì)于初學(xué)者來(lái)說(shuō),如果不怕麻煩,確實(shí)可以使用這種方法)
    這種方法只能針對(duì)多次刪除KVO的處理,原理就是try catch可以捕獲異常,不讓程序 catch。這樣就實(shí)現(xiàn)了防止多次刪除KVO。

    在dealloc方法里面執(zhí)行下面代碼(我只是舉個(gè)例子,監(jiān)聽(tīng)的對(duì)象不一樣,具體代碼也不一樣)

-(void)dealloc {
    
    NSLog(@"銷毀了");
    AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

    @try {
        [appDelegate removeObserver:self forKeyPath:@"kvoState"];

        //或者多次調(diào)用
        [appDelegate removeObserver:self forKeyPath:@"kvoState"];

    } @catch (NSException *exception) {
        NSLog(@"捕獲異常了");

    } @finally {
        NSLog(@"finally");
    }
}

上述方法基本可以解決這個(gè)崩潰的問(wèn)題,那么有沒(méi)有更好的方法解決同類的問(wèn)題呢?

  • 利用Run time
    給NSObject 增加一個(gè)分類,然后利用Run time 交換系統(tǒng)的 removeObserver方法,在里面添加 @try @catch。

  • 步驟
    創(chuàng)建一個(gè)類目category
    在銷毀KVO監(jiān)聽(tīng)對(duì)象的文件里面導(dǎo)入頭文件 #import "NSObject+MKVO.h"


#import "NSObject+MKVO.h"
#import <objc/runtime.h>

@implementation NSObject (MKVO)

+ (void)load{
    
    [self switchMethod];
    
}
+ (void)switchMethod{
    
    //移除kvo的方法
    SEL removeSel = @selector(removeObserver:forKeyPath:);
    SEL myRemoveSel = @selector(removeDasen:forKeyPath:);
    
    //監(jiān)聽(tīng)的方法
    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);
    
    //交換方法的實(shí)現(xiàn)
    method_exchangeImplementations(systemRemoveMethod, DasenRemoveMethod);
    method_exchangeImplementations(systemAddMethod, DasenAddMethod);
}

//利用@try @catch
// 交換后的方法
- (void)removeDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    
    @try {//相對(duì)應(yīng)解決方法1而已,只是把@try @catch 寫(xiě)在這里而已
    
        [self removeDasen:observer forKeyPath:keyPath];
    
    } @catch (NSException *exception) {
    
    }
}

// 交換后的方法
- (void)addDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath options:
(NSKeyValueObservingOptions)options context:(void *)context{
    
    [self addDasen:observer forKeyPath:keyPath options:options context:context];
    
}

總結(jié): 在 dealloc 方法里面,調(diào)用removeObserver:forKeyPath: 方法,其實(shí)就是調(diào)用 分類category 里面的removeDasen: forKeyPath:方法了,因?yàn)槔鎟untime,交換了這兩個(gè)方法的實(shí)現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容