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

时间:2024-10-08 12:21:59

-(void)dealloc

{

    AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

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

}

appDelegate 的属性kvoState 会被remove,但是的这个时候,it is not registered as an observer所有,就会重新上述的崩溃现象


说了这么多,大家能理解这个崩溃的原因了吗?(PS:不懂的话也请继续了解下面的内容)

总之就是:有时候我们会忘记添加多次KVO监听或者,不小心删除如果KVO监听,如果添加多次KVO监听这个时候我们就会接受到多次监听。如果删除多次kvo程序就会造成catch


既然问题的出现,那么,肯定会伴随着事务的解决,下面我讲给大家讲解几个解决的方法(百度查资料的,亲自验证,安全可靠),方案有三种:

/**

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

     *  方案一 :利用 @try @catch

     *  方案二 :利用 模型数组 进行存储记录

     *  方案二 :利用 observationInfo 里私有属性

     *

     */


《方案一》

/**

 *  方案一 :利用 @try @catch(只能针对删除多次KVO的情况下)

 *  利用 @try @catc

 不得不说这种方法真是很Low,不过很简单就可以实现。(对于初学者来说,如果不怕麻烦,确实可以使用这种方法)

    这种方法只能针对多次删除KVO的处理,原理就是try catch可以捕获异常,不让程序catch。这样就实现了防止多次删除KVO

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

-(void)dealloc

{

    //方案一 :利用 @try @catch(只能针对删除多次KVO的情况下)(解决方法1

AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

@try {

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

}

@catch (NSException *exception) {

  NSLog(@"多次删除kvo 报错了");

}

}

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

    步骤:创建一个类目NSObject+DSKVO,执行代码里面的步骤

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

         AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;

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

那么,那个类目里面的代码是这样的:(导入头文件:#import <objc/>解决方法2

+ (void)load

{

    [self switchMethod];

}


+ (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);

}

#pragma mark - 第一种方案,利用@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];


}


这种方法 利用 Run time 交换系统的 removeObserver 方法,在里面添加 @try @catch
相对上述那种解决方法来说,理解稍微难那么一点,但是,不需要移除kvo 的时候每次调用 @try @catch(这样省了很多代码)
《方案二》

(2) 方案二
利用 模型数组 进行存储记录

第一步 利用交换方法,拦截到需要的东西
1,是在监听哪个对象。
2,是在监听的keyPath是什么。

第二步 存储思路
1,我们需要一个模型用来存储
哪个对象执行了addObserver、监听的KeyPath是什么。
2,我们需要一个数组来存储这个模型。

第三步 进行存储
1,利用runtime 拦截到对象和keyPath,创建模型然后进行赋值模型相应的属性。
2,然后存储进数组中去。

第三步 存储之前的检索处理
1,在存储之前,为了防止多次addObserver相同的属性,这个时候我们就可以,遍历数组,取出每个一个模型,然后取出模型中的对象,首先判断对象是否一致,然后判断keypath是否一致2,对于添加KVO监听:如果不一致那么就执行利用交换后方法执行addObserver方法。

3,对于删除KVO监听: 如果一致那么我们就执行删除监听,否则不执行。

下面我讲介绍代码:

+ (void)load

{

    [self switchMethod];

}


+ (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);

}

上述两个方法的代码同案例1 的一样(同样是新建一个类目 NSObject+DSKVO),然后在写下面方法

#pragma mark - 第二种方案,利用私有属性

// 交换后的方法

- (void)removeDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath

{

    NSMutableArray *Observers = [DSObserver sharedDSObserver];

    ObserverData *userPathData = [self observerKeyPath:keyPath];

    // 如果有该key值那么进行删除

    if (userPathData) {

        [Observers removeObject:userPathData];

        @try {//如果没有写@try @catch 的话,在 dealloc 中,那个被监听的对象(appdelegate)必须要全局变量

            [self removeDasen:observer forKeyPath:keyPath];

        }

        @catch (NSException *exception) {

            

        }

        

    }

    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];


    // 如果没有注册,那么才进行注册

    if (![self observerKeyPath:keyPath]) {

        [Observers addObject:userPathData];

        [self addDasen:observer forKeyPath:keyPath options:options context:context];

    }


}


// 进行检索,判断是否已经存储了该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;

}

这种情况还需要新建几个文件: DSObserver 、ObserverData
——————————————————————————————————————————————————————————————

#import <Foundation/>


@interface ObserverData : NSObject

@property (nonatomic, strong)id objc;

@property (nonatomic, copyNSString *keyPath;

- (instancetype)initWithObjc:(id)objc key:(NSString *)key;

@end



#import ""


@implementation ObserverData

- (instancetype)initWithObjc:(id)objc key:(NSString *)key

{

    if (self = [super init]) {

        self.objc = objc;

        self.keyPath = key;

    }

    return self;

}

@end


---------------------------------------

#import <Foundation/>


@interface DSObserver : NSMutableArray


+ (instancetype)sharedDSObserver;

@end




#import ""


@implementation DSObserver

+ (instancetype)sharedDSObserver

{

    static id objc;

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

        objc = [NSMutableArray array];

    });

    return objc;

}


@end

上述就是方案二了