美文网首页iOS基础
iOS拦截系统KVO监听,防止多次删除和添加【it is not

iOS拦截系统KVO监听,防止多次删除和添加【it is not

作者: 那是一阵清风_徐来 | 来源:发表于2019-01-08 10:55 被阅读5次
    浅谈
    • 最近项目中处理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 <SecondViewController 0x7f8b7ef0bad0> for the key path "kvoState" from <AppDelegate 0x600002c8b020> 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 <objc/runtime.h>
    
    @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

        本文链接:https://www.haomeiwen.com/subject/gdfvrqtx.html