iOS開發中咱們會遇到程序異常退出的狀況,若是是在調試的過程當中,可能經過設施斷點或者打印關鍵信息的方式來進行調試,可是對於一些複雜模塊非必現的異常崩潰,這種方式有時難以定位問題,並且對於已經發布上線的應用,這種方式更是無能爲力。objective-c
一般咱們見到的Crash分爲兩種,一種是系統內存錯誤,觸發EXC_BAD_ACCESS引發的,程序運行過程當中訪問了錯誤的內存地址,另外一種是出現了不能被處理的signal異常,致使程序向自身發送了SIGABRT信號而崩潰,下面是我平時使用較多的crash處理方法,跟你們分享一下。架構
iOS開發中對繼承自NSObject的對象,內存管理採用引用計數機制,對於非NSObject對象,引用計數機制不起做用,須要本身管理內存的使用回收。引用計數原理是,當對象被持有一次(retain),它的引用計數(retainCount)+1,被標記釋放一次(release),retainCount -1,當retainCount爲0,對象被釋放。app
在使用手動引用計數(MRC)開發時,須要開發者顯式的調用retain或release。蘋果在iOS5以後推行自動引用計數(ARC),開發者沒必要顯式的調用retain或release了,由編譯器來自動添加,在方便開發者的同時,也下降了開發難度,同時內存出現問題的時候也更難定位錯誤了。函數
1.2.1 添加Xcode全局異常斷點學習
① 將導航器視圖切換到斷點導航器視圖下:測試
② 點擊左下角的+號,選擇Exception Breakpoint這一選項。ui
③ 異常斷點能夠編輯許多功能,例如執行腳本,輸出log,選擇只處理objective-c異常等等,功能很豐富。操作系統
下一次當咱們運行程序出現崩潰的時候,程序會自動中止在崩潰的代碼處,方便咱們查找問題。線程
1.2.2 殭屍對象調試debug
全局異常斷點一般狀況下都會把崩潰緣由定位到具體代碼中。可是,若是崩潰不在當前調用棧,系統就僅僅只能把崩潰地址告訴咱們,而沒辦法定位到具體代碼,這樣咱們也無法去修改錯誤。相似下面這種:
這種狀況下咱們能夠經過Xcode提供的殭屍對象調試(Zombie Objects)來嘗試找到問題。
① 首先仍是打開Xcode 選擇屏幕左上角Xcode-> Preferencese,在behavior選項卡中,設置一下輸出信息,調試的時候輸出更多的信息,以下截圖,勾上:
② 菜單Product > Scheme > Edit Scheme中,把紅色圈裏面的三個選項都勾上:
③ 開啓該選項後,程序在運行時,若是訪問了已經釋放的對象,則會給出較準確的定位信息,能夠幫助肯定問題所在。
該功能的原理是,在對象釋放(retainCount爲0)時,使用一個內置的Zombie對象,替代原來被釋放的對象。不管向該對象發送什麼消息(函數調用),都會觸發異常,拋出調試信息。
注意:記得在問題被修復後,關閉該功能!會引發程序內存佔用異常。
④ 也能夠經過系統terminal打印出調用信息,使用終端的mallochistory命令,例如"mallochistory 30495 0x60005ef76fd0",其中30495是該進程的pid,pid能夠根據Xcode控制檯中的log查看,或者經過活動監視器得到, 根據這個記錄,能夠大體判斷出錯誤代碼的位置。
會出現相似如下提示代碼,根據一些關鍵信息,就能夠找出錯誤具體位置。
1.2.3 利用NSSetUncaughtExceptionHandler處理
以前的兩種方式,對於線上的APP能夠說無能爲力的,還好,iOS提供了異常發生的處理API,NSSetUncaughtExceptionHandler,咱們在程序啓動的時候能夠添加這樣的Handler,這樣的程序發生異常的時候就能夠對這一部分的信息進行必要的處理,適時的反饋給開發者。須要注意的是,利用NSSetUncaughtExceptionHandler能夠用來處理異常崩潰,崩潰報告系統會用NSSetUncaughtExceptionHandler方法設置全局的異常處理器。若是自定義NSSetUncaughtExceptionHandler監聽事件,會致使第三方監聽(如Bugly)失效,已經集成了第三方監聽平臺的小夥伴須要注意。
① 註冊全局處理異常的handler,在程序啓動或者其餘入口註冊:
② 當線上程序出現crash時,代碼會執行到以前註冊的handle中,將錯誤信息保存在本地。
③ 經過保存的線上app的dSYM符號表查找問題。
iOS構建時產生的符號表,它是內存地址與函數名,文件名,行號的映射表。 符號表元素以下所示:
當應用crash時,咱們能夠利用crash時的堆棧信息獲得對應到源代碼的堆棧信息,還能看到出錯的代碼在多少行,因此能快速定位出錯的代碼位置,以便快速解決問題。
獲取到dSYM符號表和以前的程序崩潰的錯誤日誌,咱們就能夠定位問題了。
④ 利用atos命令定位問題。
atos命令來符號化某個特定模塊加載地址 atos [-arch 架構名] [-o 符號表] [-l 模塊地址] [方法地址]
使用終端計算,首先得到十六進制地址區間。
終端代碼執行:
這樣,就能夠定位出問題代碼。
Mach是Mac OS和iOS操做系統的微內核核心,Mach異常是指最底層的內核級異常,因此當APP中產生異常時,最早能監聽到異常的就是Mach。
最早捕獲到異常的Mach在接下來會將全部的異常轉換爲相應的Unix信號,並投遞到出錯的線程。以後就能夠註冊想要監聽的signal類型,來捕獲信號。使用Objective-C的異常處理是不能獲得signal的,若是要處理它,咱們還要利用unix標準的signal機制,註冊SIGABRT, SIGBUS, SIGSEGV等信號發生時的處理函數。該函數中咱們能夠輸出棧信息,版本信息等其餘一切咱們所想要的。以下,就是監聽了SIGSEGV信號,當有SIGSEGV信號產生時,就會回調mySignalHandler方法:signal (SIGSEGV, mySignalHandler)。
信號默認的處理方法一共有五種,分別用Terminate (terminate process,即結東進程)、Ignore(忽略該信號)、Dump(terminate process and dump core:結束進程並生成 core dump,將進程的內存信息打印出來),Stop(進程暫停運行,多用於調試)以及 Cont(恢復運行個以前被暫停的進程,多用於調試)來表示。
Signal信號類型:
信號名稱 |
默認處理 |
說明 |
SIGABRT |
Dump |
程序終止命令 |
SIGALRM |
Terminate |
程序超時信號 |
SIGILL |
Dump |
程序非法指令信號 |
SIGHUP |
Terminate |
程序終端停止信號 |
SIGINT |
Terminate |
程序鍵盤中斷信號 |
SIGKILL |
Terminate |
程序強制結束信號 |
SIGTERM |
Terminate |
程序終止信號 |
SIGSTOP |
Stop |
程序鍵盤停止信號 |
SIGSEGV |
Dump |
程序無效內存停止信號 |
SIGBUS |
Dump |
程序內存字節未對齊停止信號 |
SIGPIPE |
Terminate |
程序Socket發送失敗停止信號 |
若是沒有爲一個信號設置對應的處理函數,就會使用默認的處理函數,不然信號就被進程截獲並調用相應的處理函數。在沒有處理函數的狀況下,程序能夠指定兩種行爲:忽略這個信號 SIG_IGN 或者用默認的處理函數 SIG_DFL 。可是有兩個信號是沒法被截獲並處理的: SIGKILL、SIGSTOP 。
① 註冊全局處理異常signal的handler,在程序啓動或者其餘入口註冊。
有關錯誤類型能夠看上面的說明,SignalExceptionHandler是信號出錯時候的回調。當有信號出錯的時候,能夠回調到這個方法。
② SignalHandler不要在debug環境下測試。由於系統的debug會優先去攔截。我在模擬器上運行一次後,關閉debug狀態,而後直接在模擬器上點擊咱們build上去的app去運行。得到以下的日誌:
對於應用crash,有不少第三方優秀平臺(友盟,bugly等)提供日誌和打點功能,已經可以知足平常開發須要,可是學習這些經常使用的crash仍是可以幫助咱們理解iOS運行機制,以上這些是開發中常常見到的一些crash,實際處理中可能狀況複雜,須要多種方式同時使用才能定位問題,靈活使用。
張力,民生科技有限公司,用戶體驗技術部開發工程師。