浅谈
- 最近项目中处理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,交换了这两个方法的实现