常見的 Crash 場景
- 訪問了僵尸對象
- 訪問了不存在的方法
unrecognized selector sent to instance - 數(shù)組越界
- 在定時器下一次回調(diào)前將定時器釋放,會Crash
- KVC造成的crash
- EXC_BAD_ACCESS
- KVO引起的崩潰
- 多線程中的崩潰
- Socket長連接,進入后臺沒有關(guān)閉
- Watch Dog超時造成的crash
什么時候會報 unrecognized selector 異常
當調(diào)用對象(子類,各級父類)中不含有對應(yīng)方法的時候,并且依舊沒有給出“消息轉(zhuǎn)發(fā)”的具體方案的時候,程序在運行時會crash并拋出 unrecognized selector 異常
OC 中的每個方法在運行時會被轉(zhuǎn)為消息發(fā)送objc_msgSend(reciver, selector)
例如 [person say]就會被轉(zhuǎn)化為 objc_msgSend(person, @selector(say))
運行時會根據(jù)對象(reciever) 的isa 指針找到該對象所對應(yīng)的類,然后會依次在對應(yīng)的 類,父類,爺爺類,根類中找對應(yīng)的方法
下面講述對象方法的解析過程:
- 第一步:
+(BOOL)resolveInstanceMethod:(SEL)sel``實現(xiàn)方法,指定是否動態(tài)添加方法。 若返回NO,則進入下一步,若返回YES,則通過class_addMethod`函數(shù)動態(tài)地添加方 法,消息得到處理,此流程完畢。 - 第二步:在第一步返回的是
NO時,就會進入- (id)forwardingTargetForSelector:(SEL)aSelector方法,這是運行時給我們的第二次機會,用于指定哪個對象響應(yīng)這個selector。不能指定為self。若返回nil,表示沒有響應(yīng) 者,則會進入第三步。若返回某個對象,則會調(diào)用該對象的方法。 - 第三步:若第二步返回的是
nil,則我們首先要通過- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector指定方法簽名,若返回nil,則表示不處 理。若返回方法簽名,則會進入下一步。 - 第四步:當?shù)谌椒祷胤椒ǚ椒ê灻螅蜁{(diào)用
- (void)forwardInvocation:(NSInvocation *)anInvocation方法,我們可以通過anInvocation對象做很多處理,比如修改實現(xiàn)方法,修改響應(yīng)對象等 - 第五步:若沒有實現(xiàn)
- (void)forwardInvocation:(NSInvocation *)anInvocation方法,那么 會進入- (void)doesNotRecognizeSelector:(SEL)aSelector方法。若我們沒有實現(xiàn)這個方 法,那么就會crash,然后提示打不到響應(yīng)的方法。到此,動態(tài)解析的流程就結(jié)束了。
數(shù)組越界
在數(shù)組取值時加判斷
if array.count > index {
let value = array[index]
}
KVC造成的crash
給不存在的key(包括key為nil)設(shè)置value
關(guān)于EXC_BAD_ACCESS
出現(xiàn)的原因: 訪問了野指針, 比如訪問已經(jīng)釋放對象的成員變量或者發(fā)消息, 死循環(huán)等;
解決方法:
重寫對象的
respondsToSelector方法, 先找到出現(xiàn) EXECBADACCESS 前訪問的最后一個 object;-
設(shè)置Enable Zombie Objects;Zombie
設(shè)置全局斷點快速定位問題代碼所在行,接收所有異常;
-
Xcode7 之后已經(jīng)集成了 BAD_ACCESS 捕獲功能: Address Sanitizer 與步驟 2 一樣設(shè)置;Address Sanitizer
analyze(靜態(tài)分析, 不一定管用)
如何解決很難復(fù)現(xiàn)的crash
- 1.有錯誤日志先看錯誤日志信息
- 2.沒有錯誤日志,第一步分析函數(shù)中的所有分支, 是否在語法上存在可能缺少條件的問題.所以檢查所以的分支,確保每個分支執(zhí)行的結(jié)果是正確的.
- 3.檢查函數(shù)的參數(shù),保證必傳參數(shù)不能為空,若為空應(yīng)該拋出異常,因此用斷言檢查參數(shù)的正確性很重要
- 4.檢查函數(shù)中每個分支所調(diào)用的函數(shù)返回結(jié)果是正確的,其實就是遞歸過程(重復(fù)2,3步驟)
KVO引起的崩潰
- 觀察者是局部變量,會崩潰
- 被觀察者是局部變量,會崩潰
- 沒有實現(xiàn)
observeValueForKeyPath:ofObject:changecontext:方法:,會崩潰 - 重復(fù)移除觀察者,會崩潰
- 重復(fù)添加觀察者,不會崩潰,但是添加多少次,一次改變就會被觀察多少次
多線程中的崩潰
多線程遇到需要同步的時候,加鎖,添加信號量等進行同步操作。一般多線程發(fā)生的Crash,會收到SIGSEGV信號,表明試圖訪問未分配給自己的內(nèi)存, 或試圖往沒有寫權(quán)限的內(nèi)存地址寫數(shù)據(jù)。
- 死鎖、子線程中更新UI、多個線程同時釋放一個對象
- 在子線程中更新UI
-
dispatch_groupcrash,dispatch_group_leave的次數(shù)比dispatch_group_enter次數(shù)多 - 多線程下非線程安全類的使用,如
NSMutableArray、NSMutableDictionary。NSCache是線程安全的。 - 數(shù)據(jù)緩存到磁盤和讀取。
Socket長連接,進入后臺沒有關(guān)閉
當服務(wù)器close一個連接時,若client端接著發(fā)數(shù)據(jù)。根據(jù)TCP協(xié)議的規(guī)定,會收到一個RST響應(yīng),client再往這個服務(wù)器發(fā)送數(shù)據(jù)時,系統(tǒng)會發(fā)出一個SIGPIPE信號給進程,告訴進程這個連接已經(jīng)斷開了,不要再寫了。而根據(jù)信號的默認處理規(guī)則,SIGPIPE信號的默認執(zhí)行動作是terminate(終止、退出),所以client會退出。
長連接socket或重定向管道進入后臺,沒有關(guān)閉導致崩潰的解決辦法:
- 切換到后臺是,關(guān)閉長連接和管道,回到前臺重新創(chuàng)建。
- 使用signal(SIGPIPE,SIG_IGN),將SIGPIP交給系統(tǒng)處理,這么做將SIGPIPE設(shè)為SIG_IGN,使客戶端不執(zhí)行默認操作,即不退出。
Watch Dog超時造成的crash
主線程執(zhí)行耗時操作,導致主線程被卡超過一定時間。一般異常編碼是0x8badf00d,表示應(yīng)用發(fā)生watch dog超時而被iOS終止,通常是應(yīng)用花費太多的時間無法啟動、終止或者響應(yīng)系統(tǒng)事件。
解決:主線程只負責更新UI和事件響應(yīng),將耗時操作(網(wǎng)絡(luò)請求、數(shù)據(jù)庫讀寫等)異步放到后臺線程執(zhí)行。

