背景:
最近在整理bugly上的問題,順便整理記錄下來,方便后面開發(fā)的時候避免犯一樣的錯誤.ios現(xiàn)在的系統(tǒng)版本都到13點幾了,而我們app之前支持到ios8,后面升級到ios10.所以我們解決的問題主要還是針對ios10及以上.在解決問題的同時發(fā)現(xiàn)ios11以下也就是ios10點多有很大的差別.或者說ios11開始系統(tǒng)做了好多容錯處理,好多本來應(yīng)該閃退的在11及以上版本都不閃退了.如后面會提到的OKV坏为、通知等監(jiān)聽,鍵盤和數(shù)組safe的沖突導(dǎo)致的閃退.
關(guān)于NSExceptionName:
常見的就4種,其他的至少在我們項目沒見過.下面就先根據(jù)異常名字簡單翻譯理解下.
NSGenericException:一般異常
NSMallocException:內(nèi)存不足異常
NSObjectInaccessibleException:對象不可訪問異常
NSObjectNotAvailableException:對象不可用異常
NSDestinationInvalidException:目標(biāo)無效異常
NSPortTimeoutException:端口超時異常
NSInvalidSendPortException:無效發(fā)送端口異常
NSInvalidReceivePortException:無效接收端口異常
NSPortSendException:端口發(fā)送異常
NSPortReceiveException:端口接收異常
NSOldStyleException:老樣式異常
重點分享4類異常:
NSRangeException:范圍異常/越界異常
問題:
使用的數(shù)組下標(biāo)超出數(shù)組最大下標(biāo)值
比如數(shù)組長度count, index的下標(biāo)范圍[0, count -1], 在開發(fā)時,可能index的最大值超過數(shù)組的范圍
這可能是不同模塊的處理沒有對應(yīng)上
使用的數(shù)組下標(biāo)是一個非正常值
如果小標(biāo)(index)的值是由其他模塊的變量傳遞進(jìn)來的镊绪,這就會有很大的不確定性匀伏, 可能是一個很大的整數(shù)值
很明顯,上面的值分別是32位和64位下的最大整數(shù)值蝴韭,可能的情況就是傳遞的 index 參數(shù)獲取了無效的值够颠,但仍然拿到 NSMutableArray 中使用
比如 index 通過下面的方式獲取
如果找不到 str ,則返回 NSNotFound榄鉴,32位下它就是2147483647履磨,64位下18446744073709551615 蛉抓,將它作為參數(shù)傳遞則會導(dǎo)致 NSRangeException。
Cannot remove an observer for the key path"currentViewController.viewModel.selectedGoodsCount" from because it is not registered as an observer.
[self addObserver:self forKeyPath:@"currentVC.viewModel.selectedGoodsCount" options:NSKeyValueObservingOptionNew context:nil];
[self removeObserver:self forKeyPath:@"currentVC.viewModel.selectedGoodsCount"];
空數(shù)組的操作
如果一個數(shù)組剛剛初始化剃诅,還是空的巷送,就對它進(jìn)行相關(guān)讀取操作
處理的數(shù)據(jù)范圍 NSRange 超過數(shù)據(jù)本身的長度
-[__NSArrayM removeObjectsInRange:]: range {1, 1} extends beyond bounds [0 .. 0]
Cannot remove an observer for the key path"currentViewController.viewModel.selectedGoodsCount" from because it is not registered as an observer.
[self addObserver:self forKeyPath:@"currentVC.viewModel.selectedGoodsCount" options:NSKeyValueObservingOptionNew context:nil];
[self removeObserver:self forKeyPath:@"currentVC.viewModel.selectedGoodsCount"];
KVO監(jiān)聽移除了2次,或還沒添加就運行了移除,也會報數(shù)據(jù)越界.一般移除代碼會寫在dealloc方法,添加代碼比較習(xí)慣寫到viewDidLoad方法.這樣正常情況下是不會有問題的,但是極端情況下,手機卡了等情況如果導(dǎo)致你的VC創(chuàng)建了,當(dāng)沒有能加載完成顯示出來就直接回收了,就會導(dǎo)致沒有添加監(jiān)聽直接走移除監(jiān)聽而導(dǎo)致上面出現(xiàn)的閃退.所以這里給的建議是可以把監(jiān)聽的添加代碼放在init方法處理,讓生命周期對稱使用.或用runtime原理來做統(tǒng)一的交換safe保護下哪怕移除了多次監(jiān)聽也不讓閃退.
NSInvalidArgumentException:無效參數(shù)異常
1.運行時插入的Object為nil
2.或者調(diào)用一個沒有實現(xiàn)的方法
3.performSegue但是沒有在storyboard里面連線
4.返回的是id類型強轉(zhuǎn)為另外一個類型,并調(diào)用方法出現(xiàn)找不到
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:'-[__NSDictionaryI setObject:forKey:]: unrecognized selector sent to instance 0x60000389e500'
NSString *result=@"{\"username\":\"aaa\",\"phone\":\"18273948475\",\"bankcount\":\"k3232323\"}";
NSData *data = [result dataUsingEncoding:NSUTF8StringEncoding];
NSMutableDictionary *info = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
if (info) {
? ? ? NSString *username = info[@"username"];//[Utils UrlDecode: info[@"username"]];
? ? ? [info setObject:username forKey:@"username"];
}
從JSON數(shù)據(jù)創(chuàng)建一個Foundation對象。如果解析器應(yīng)該允許不是NSArray或NSDictionary的頂級對象矛辕,請設(shè)置NSJSONReadingAllowFragments選項笑跛。設(shè)置NSJSONReadingMutableContainers選項將使解析器生成可變NSArray和NSDictionary。設(shè)置NSJSONReadingMutableLeaves選項將使解析器生成可變的NSString對象聊品。如果在解析過程中發(fā)生錯誤堡牡,那么將設(shè)置錯誤參數(shù),結(jié)果將為零杨刨。
UITabBarController *tabBarController = (UITabBarController *)[UIApplication sharedApplication].keyWindow.rootViewController;
UINavigationController *navigationController = tabBarController.selectedViewController;
像這種強轉(zhuǎn)就要特別注意,如果當(dāng)前的rootViewController不是UITabBarController類型就會出現(xiàn)閃退.
正常寫代碼肯定不會這樣寫,但是“l(fā)oc_str”這個參數(shù)一般是上個頁面給或服務(wù)接口給的,這樣就不可控了,只要出現(xiàn)nil的情況就會閃退.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:'NSConcreteMutableAttributedString initWithString:: nil value'
NSString *loc_str=nil;
NSMutableAttributedString *attriutedString = [[NSMutableAttributedString alloc] initWithString:loc_str];
在對象調(diào)用了自己沒有的屬性或方法是發(fā)生的閃退:如UIView 調(diào)用了 UIViewController 的方法時發(fā)生的閃退晤柄。
一般情況下不會發(fā)生類似問題,主要是在頁面?zhèn)髦祷蛘呤褂脠?zhí)行集中跳轉(zhuǎn)邏輯的時候由于傳參不合適或者是代碼不規(guī)范造成問題妖胀。
*** -[UIWindow navigationController]: unrecognized selector sent to instance 0x101b07d60
if (! [viewController isKindOfClass:[UIViewController class]]) return;
[viewController.navigationController pushViewController:[self needJumpToVCName:vcClassName withParameter:parameter] animated:YES];
在構(gòu)造 NSMutableAttributedString 或者 NSAttributedString 設(shè)置的屬性值為 nil 導(dǎo)致閃退芥颈。
這個問題比較容易忽略,在實例化該對象是一定要小心赚抡、規(guī)范爬坑。
*** NSConcreteMutableAttributedString initWithString:: nil value
NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString: originStr];
NSInternalInconsistencyException:內(nèi)部不一致異常/內(nèi)部矛盾異常
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',reason: 'Could not load NIB in bundle: 'NSBundle
NSArray*nib = [[NSBundle mainBundle] loadNibNamed:@"CellForOffers" owner:self options:nil];
loadNibNamed的xib不存在,出現(xiàn)NSInternalInconsistencyException錯誤
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',reason: 'attempt to delete row 13 from section 0 which only contains 12 rows before the update'
- (void)insertRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)deleteRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation;
- (void)reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation API_AVAILABLE(ios(3.0));
- (void)moveRowAtIndexPath:(NSIndexPath *)indexPath toIndexPath:(NSIndexPath *)newIndexPath API_AVAILABLE(ios(5.0));
使用這4個方法要特別注意,它也數(shù)組一樣容易越界,屬于tableview的頁面row越界
Invalid update: invalid number of rows in section 0.The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (3),plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted)and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
這種情況一般是數(shù)據(jù)源變化了,操作的時候和之前的數(shù)據(jù)源不一致了,一般出現(xiàn)在一些業(yè)務(wù)場景異步處理數(shù)據(jù)源.比如消息,網(wǎng)絡(luò)請求等
An instance 0x102471600 of class YYTextView was deallocated while key value observers were still registered with it. Current observation info:<NSKeyValueObservationInfo 0x174a20e60> ( <NSKeyValueObservance 0x174c59680: Observer: 0x174205ba0, Key path: contentSize, Options:?<New: YES, Old: YES, Prior: NO> Context: 0x0, Property: 0x174c59740> )
@weakify(self)
[self.textInputView.textView addObserverBlockForKeyPath:@"contentSize" block:^(YYTextView *obj, id _Nullable oldVal, id _Nullable newVal) {
? ????????@strongify(self)
? ? ? ? ? ?if (!self) return ;
}];
沒寫 [self.textInputView.textView removeObserverBlocks];ios11以下版本,會導(dǎo)致閃退
addObserverBlockForKeyPath卻忘記移除,當(dāng)這個類self回收了,再次觸發(fā)監(jiān)聽就會出現(xiàn)找不到改對象而出現(xiàn)閃退.類似的監(jiān)聽還有通知、KVO都會導(dǎo)致閃退.需要注意的下在ios11以下都會閃退,親測試ios11及以上不再會閃退,如果你的app需要兼容的系統(tǒng)版本是11及以上可以不需要寫,但是為了研發(fā)規(guī)范和習(xí)慣,建議都寫.
關(guān)于SIGSEGV
SIG 是信號名的通用前綴, SEGV 是 segmentation violation 的縮寫
在 POSIX 兼容的平臺上涂臣,SIGSEGV 是當(dāng)一個進(jìn)程執(zhí)行了一個無效的內(nèi)存引用盾计,或發(fā)生段錯誤時發(fā)送給它的信號。SIGSEGV 的符號常量在頭文件 signal.h 中定義赁遗。因為在不同平臺上,信號數(shù)字可能變化岩四,因此符號信號名被使用哭尝。通常剖煌,它是信號11。
閃退場景一:recorder deleteRecording 之前 先判斷文件是否存在耕姊,否則會造成過度釋放桶唐,解決方法:
if ([[NSFileManager defaultManager] fileExistsAtPath:self.recorder.url.path]) {
????if (![self.recorder deleteRecording])
????????????NSLog(@"Failed to delete %@", self.recorder.url);
}
閃退場景二: delegate = nil 。
將XXViewContrller設(shè)置為delegate時茉兰,當(dāng)頁面發(fā)生跳轉(zhuǎn)時尤泽,XXViewController的對象會被釋放,這是代碼走到[_delegate callbackMethod],便出現(xiàn)crash。解決方法有二:1.將@property (nonatomic ,assign) id <BLELibDelegate>delegate; 中 assign關(guān)鍵字改為weak安吁。 2.在XXViewController的delloc方法中添加:xxx.delegate = nil;
閃退場景三:鍵盤和使用了method swizzle將NSArray和NSMutableArray的objectAtIndex:等其他類進(jìn)行數(shù)據(jù)安全導(dǎo)致的
不管行為軌跡是如何的,在哪個頁面閃退,最后的本質(zhì)應(yīng)該都是一樣的.ios11以下版本,我們目前報過來的基本是10.3.3和少量的10.3.2、10.3.1 由于沒有真機,我們用模擬機模擬,如果不開啟Xcode的Zombie Objects,不是畢現(xiàn)的,很少機率偶發(fā)的.調(diào)試的時候可以開啟Xcode的Zombie Objects,并找到能彈出鍵盤的界面中燃辖,鍵盤顯示的情況下 home app 進(jìn)入后臺鬼店,再單擊app 圖標(biāo) 切換回前臺時 發(fā)生crash.
處理:Safe文件設(shè)置為掛鉤NSMutableArray方法編譯器標(biāo)志的位置 -fno-objc-arc 或者不使用方法交換的方式來做safe處理.
Methodmethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"),@selector(objectAtIndex:));Method method2 = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"),?@selector(safeObjectAtIndex:));method_exchangeImplementations(method, method2);
去調(diào)類似代碼,讓開發(fā)人員規(guī)范開發(fā)不依賴safe類.
好了就寫到這里了,這就是我們項目中發(fā)現(xiàn)的一些問題,后面有新的問題再來補充,或大家有啥發(fā)現(xiàn)也可以來補充.