iOS拦截系统KVO监听,防止多次删除和添加【it is not registered as an observer.】

浅谈
  • 最近项目中处理kvo 的时候,遇到一个问题:当我操作的时候,会发现kvo 释放的时候,会崩溃, 崩溃日志如下

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

经过反复研究,发现了错误的原因,并且找到解决错误的办法下面我将介绍一下我的思路:(慢慢来 跟着我的思路走)

  • 1、我在AppDelegate里面添加一个属性
//测试kvo设置的一个字段 
@property(nonatomic,copy)NSString *kvoState;
  • 2、我在我创建的一个ViewController(SecondViewController)里面去监听这个属性,但是没有 调用monitorNet 方法
- (void)monitorNet {
    
    AppDelegate *appDelegate = (AppDelegate *)[UIApplication  sharedApplication].delegate;    // kvo监听属性值的改变
    [appDelegate addObserver:self forKeyPath:@"kvoState" options:NSKeyValueObservingOptionNew context:nil];
}

/**
 *  KVO 监听方法
 *
 *  @param keyPath 监听的属性名称
 *  @param object 被监听的对象
 *  @param change 属性的值
 *  @param context 添加监听时传来的值
 */
- (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、然后我再去释放 复写系统 dealloc 这个方法
-(void)dealloc {
    NSLog(@"销毁了");
    AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
    
    [appDelegate removeObserver:self forKeyPath:@"kvoState"];
    
    //或者多次调用
    [appDelegate removeObserver:self forKeyPath:@"kvoState"];

}
  • 4、在第二步之后,我点击一个button ,push 到 另外一个ViewController(TestViewController)里面,然后在TestViewController里面,点击button ,在这个button 的点击事件里面去执行下面的代码:(特地演示错误)
- (IBAction)btnAction:(id)sender {
    
    SecondViewController *vc = [[SecondViewController alloc] init];
    [self.navigationController pushViewController:vc  animated:YES];
}

当这个方法执行完之后,就会出现前面所展示的错误

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

为什么会出现这种错误呢????其实出现这种错误也很简单的:
首先在buttonAction 这个方法内,secondVC 他是一个局部变量,现在是ARC 管理,当这个方法执行完成以后,会销毁 secondVC 这个对象,那么,很自然的就会调用 SecondViewController 里面的 dealloc 这个方法【也就是第三步的方法,请看第三步】

解释:

根据错误提示,appDelegate 的属性kvoState 会被remove,但是的这个时候,it is not registered as an observer,所以,就会出现上述的崩溃现象说了这么多,大家能理解这个崩溃的原因了吗?(PS:不懂的话也请继续了解下面的内容)

总之就是:有时候我们会忘记添加多次KVO监听或者,不小心删除如果KVO监听,如果添加多次KVO监听这个时候我们就会接受到多次监听。如果删除多次kvo程序就会造成catch既然问题的出现,那么,肯定会伴随着事务的解决

下面我讲给大家讲解几个解决的方法(百度查资料的,亲自验证,安全可靠),

方案有三种:

那么iOS开发-黑科技防止多次添加删除KVO出现的问题

  • 方案一 :利用 @try @catch

  • 利用 @try @catch(只能针对删除多次KVO的情况下)

    利用 @try @catc 不得不说这种方法真是很Low,不过很简单就可以实现。(对于初学者来说,如果不怕麻烦,确实可以使用这种方法)
    这种方法只能针对多次删除KVO的处理,原理就是try catch可以捕获异常,不让程序 catch。这样就实现了防止多次删除KVO。

    在dealloc方法里面执行下面代码(我只是举个例子,监听的对象不一样,具体代码也不一样)

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

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

        //或者多次调用
        [appDelegate removeObserver:self forKeyPath:@"kvoState"];

    } @catch (NSException *exception) {
        NSLog(@"捕获异常了");

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

上述方法基本可以解决这个崩溃的问题,那么有没有更好的方法解决同类的问题呢?

  • 利用Run time
    给NSObject 增加一个分类,然后利用Run time 交换系统的 removeObserver方法,在里面添加 @try @catch。

  • 步骤
    创建一个类目category
    在销毁KVO监听对象的文件里面导入头文件 #import "NSObject+MKVO.h"


#import "NSObject+MKVO.h"
#import 

@implementation NSObject (MKVO)

+ (void)load{
    
    [self switchMethod];
    
}
+ (void)switchMethod{
    
    //移除kvo的方法
    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);
}

//利用@try @catch
// 交换后的方法
- (void)removeDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath{
    
    @try {//相对应解决方法1而已,只是把@try @catch 写在这里而已
    
        [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];
    
}

总结: 在 dealloc 方法里面,调用removeObserver:forKeyPath: 方法,其实就是调用 分类category 里面的removeDasen: forKeyPath:方法了,因为利益runtime,交换了这两个方法的实现

你可能感兴趣的:(iOS拦截系统KVO监听,防止多次删除和添加【it is not registered as an observer.】)