淺談
- 最近項(xiàng)目中處理kvo 的時(shí)候,遇到一個(gè)問(wèn)題:當(dāng)我操作的時(shí)候,會(huì)發(fā)現(xiàn)kvo 釋放的時(shí)候,會(huì)崩潰, 崩潰日志如下
Terminating app due to uncaught exception 'NSRangeException', reason: 'Cannot remove an observerfor the key path "kvoState" frombecause it is not registered as an observer.'
經(jīng)過(guò)反復(fù)研究,發(fā)現(xiàn)了錯(cuò)誤的原因,并且找到解決錯(cuò)誤的辦法下面我將介紹一下我的思路:(慢慢來(lái) 跟著我的思路走)
- 1、我在AppDelegate里面添加一個(gè)屬性
//測(cè)試kvo設(shè)置的一個(gè)字段
@property(nonatomic,copy)NSString *kvoState;
- 2、我在我創(chuàng)建的一個(gè)ViewController(SecondViewController)里面去監(jiān)聽(tīng)這個(gè)屬性,但是
沒(méi)有 調(diào)用monitorNet 方法
- (void)monitorNet {
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate; // kvo監(jiān)聽(tīng)屬性值的改變
[appDelegate addObserver:self forKeyPath:@"kvoState" options:NSKeyValueObservingOptionNew context:nil];
}
/**
* KVO 監(jiān)聽(tīng)方法
*
* @param keyPath 監(jiān)聽(tīng)的屬性名稱
* @param object 被監(jiān)聽(tīng)的對(duì)象
* @param change 屬性的值
* @param context 添加監(jiān)聽(tīng)時(shí)傳來(lái)的值
*/
- (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、然后我再去釋放 復(fù)寫(xiě)系統(tǒng) dealloc 這個(gè)方法
-(void)dealloc {
NSLog(@"銷毀了");
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
[appDelegate removeObserver:self forKeyPath:@"kvoState"];
//或者多次調(diào)用
[appDelegate removeObserver:self forKeyPath:@"kvoState"];
}
- 4、在第二步之后,我點(diǎn)擊一個(gè)button ,push 到 另外一個(gè)ViewController(TestViewController)里面,然后在TestViewController里面,點(diǎn)擊button ,在這個(gè)button 的點(diǎn)擊事件里面去執(zhí)行下面的代碼:(特地演示錯(cuò)誤)
- (IBAction)btnAction:(id)sender {
SecondViewController *vc = [[SecondViewController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
當(dāng)這個(gè)方法執(zhí)行完之后,就會(huì)出現(xiàn)前面所展示的錯(cuò)誤
*** 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:
為什么會(huì)出現(xiàn)這種錯(cuò)誤呢????其實(shí)出現(xiàn)這種錯(cuò)誤也很簡(jiǎn)單的:
首先在buttonAction 這個(gè)方法內(nèi),secondVC 他是一個(gè)局部變量,現(xiàn)在是ARC 管理,當(dāng)這個(gè)方法執(zhí)行完成以后,會(huì)銷毀 secondVC 這個(gè)對(duì)象,那么,很自然的就會(huì)調(diào)用 SecondViewController 里面的 dealloc 這個(gè)方法【也就是第三步的方法,請(qǐng)看第三步】
解釋:
根據(jù)錯(cuò)誤提示,appDelegate 的屬性kvoState 會(huì)被remove,但是的這個(gè)時(shí)候,it is not registered as an observer,所以,就會(huì)出現(xiàn)上述的崩潰現(xiàn)象說(shuō)了這么多,大家能理解這個(gè)崩潰的原因了嗎?(PS:不懂的話也請(qǐng)繼續(xù)了解下面的內(nèi)容)
總之就是:有時(shí)候我們會(huì)忘記添加多次KVO監(jiān)聽(tīng)或者,不小心刪除如果KVO監(jiān)聽(tīng),如果添加多次KVO監(jiān)聽(tīng)這個(gè)時(shí)候我們就會(huì)接受到多次監(jiān)聽(tīng)。如果刪除多次kvo程序就會(huì)造成catch既然問(wèn)題的出現(xiàn),那么,肯定會(huì)伴隨著事務(wù)的解決
下面我講給大家講解幾個(gè)解決的方法(百度查資料的,親自驗(yàn)證,安全可靠),
方案有三種:
那么iOS開(kāi)發(fā)-黑科技防止多次添加刪除KVO出現(xiàn)的問(wèn)題
方案一 :利用 @try @catch
-
利用 @try @catch(只能針對(duì)刪除多次KVO的情況下)
利用 @try @catc 不得不說(shuō)這種方法真是很Low,不過(guò)很簡(jiǎn)單就可以實(shí)現(xiàn)。(對(duì)于初學(xué)者來(lái)說(shuō),如果不怕麻煩,確實(shí)可以使用這種方法)
這種方法只能針對(duì)多次刪除KVO的處理,原理就是try catch可以捕獲異常,不讓程序 catch。這樣就實(shí)現(xiàn)了防止多次刪除KVO。在dealloc方法里面執(zhí)行下面代碼(我只是舉個(gè)例子,監(jiān)聽(tīng)的對(duì)象不一樣,具體代碼也不一樣)
-(void)dealloc {
NSLog(@"銷毀了");
AppDelegate *appDelegate = (AppDelegate *)[UIApplication sharedApplication].delegate;
@try {
[appDelegate removeObserver:self forKeyPath:@"kvoState"];
//或者多次調(diào)用
[appDelegate removeObserver:self forKeyPath:@"kvoState"];
} @catch (NSException *exception) {
NSLog(@"捕獲異常了");
} @finally {
NSLog(@"finally");
}
}
上述方法基本可以解決這個(gè)崩潰的問(wèn)題,那么有沒(méi)有更好的方法解決同類的問(wèn)題呢?
利用Run time
給NSObject 增加一個(gè)分類,然后利用Run time 交換系統(tǒng)的 removeObserver方法,在里面添加 @try @catch。步驟
創(chuàng)建一個(gè)類目category
在銷毀KVO監(jiān)聽(tīng)對(duì)象的文件里面導(dǎo)入頭文件#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)聽(tīng)的方法
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);
//交換方法的實(shí)現(xiàn)
method_exchangeImplementations(systemRemoveMethod, DasenRemoveMethod);
method_exchangeImplementations(systemAddMethod, DasenAddMethod);
}
//利用@try @catch
// 交換后的方法
- (void)removeDasen:(NSObject *)observer forKeyPath:(NSString *)keyPath{
@try {//相對(duì)應(yīng)解決方法1而已,只是把@try @catch 寫(xiě)在這里而已
[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];
}
總結(jié): 在
dealloc方法里面,調(diào)用removeObserver:forKeyPath: 方法,其實(shí)就是調(diào)用 分類category 里面的removeDasen: forKeyPath:方法了,因?yàn)槔鎟untime,交換了這兩個(gè)方法的實(shí)現(xiàn)