淺談
- 最近項目中處理kvo 的時候渐逃,遇到一個問題:當我操作的時候,會發(fā)現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.'
經過反復研究,發(fā)現了錯誤的原因赊堪,并且找到解決錯誤的辦法下面我將介紹一下我的思路:(慢慢來 跟著我的思路走)
- 1面殖、我在AppDelegate里面添加一個屬性
//測試kvo設置的一個字段
@property(nonatomic,copy)NSString *kvoState;
- 2、我在我創(chuàng)建的一個ViewController(SecondViewController)里面去監(jiān)聽這個屬性哭廉,但是
沒有 調用monitorNet 方法
- (void)monitorNet {
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; // kvo監(jiān)聽屬性值的改變
[appDelegate addObserver:self forKeyPath:@"kvoState" options:NSKeyValueObservingOptionNew context:nil];
}
/**
* KVO 監(jiān)聽方法
*
* @param keyPath 監(jiān)聽的屬性名稱
* @param object 被監(jiān)聽的對象
* @param change 屬性的值
* @param context 添加監(jiān)聽時傳來的值
*/
- (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脊僚、然后我再去釋放 復寫系統(tǒng) 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 的點擊事件里面去執(zhí)行下面的代碼:(特地演示錯誤)
- (IBAction)btnAction:(id)sender {
SecondViewController *vc = [[SecondViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
當這個方法執(zhí)行完之后,就會出現前面所展示的錯誤
*** 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 管理荣病,當這個方法執(zhí)行完成以后码撰,會銷毀 secondVC 這個對象,那么个盆,很自然的就會調用 SecondViewController 里面的 dealloc 這個方法【也就是第三步的方法脖岛,請看第三步】
解釋:
根據錯誤提示,appDelegate 的屬性kvoState 會被remove颊亮,但是的這個時候柴梆,it is not registered as an observer
,所以终惑,就會出現上述的崩潰現象說了這么多绍在,大家能理解這個崩潰的原因了嗎?(PS:不懂的話也請繼續(xù)了解下面的內容)
總之就是:有時候我們會忘記添加多次KVO監(jiān)聽或者,不小心刪除如果KVO監(jiān)聽偿渡,如果添加多次KVO監(jiān)聽這個時候我們就會接受到多次監(jiān)聽臼寄。如果刪除多次kvo程序就會造成catch既然問題的出現,那么溜宽,肯定會伴隨著事務的解決
下面我講給大家講解幾個解決的方法(百度查資料的吉拳,親自驗證,安全可靠)适揉,
方案有三種:
那么iOS開發(fā)-黑科技防止多次添加刪除KVO出現的問題
方案一 :利用 @try @catch
-
利用 @try @catch(只能針對刪除多次KVO的情況下)
利用 @try @catc 不得不說這種方法真是很Low留攒,不過很簡單就可以實現。(對于初學者來說涡扼,如果不怕麻煩稼跳,確實可以使用這種方法)
這種方法只能針對多次刪除KVO的處理盟庞,原理就是try catch可以捕獲異常吃沪,不讓程序 catch。這樣就實現了防止多次刪除KVO什猖。在dealloc方法里面執(zhí)行下面代碼(我只是舉個例子票彪,監(jiān)聽的對象不一樣,具體代碼也不一樣)
-(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 交換系統(tǒng)的 removeObserver方法摇零,在里面添加 @try @catch推掸。步驟
創(chuàng)建一個類目category
在銷毀KVO監(jiān)聽對象的文件里面導入頭文件#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)聽的方法
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噪服,交換了這兩個方法的實現