iOS应用崩溃日志分析 iOS应用崩溃日志揭秘
轉自:http://www.raywenderlich.com/zh-hans/30818/ios%E5%BA%94%E7%94%A8%E5%B4%A9%E6%BA%83%E6%97%A5%E5%BF%97%E6%8F%AD%E7%A7%98
這篇文章還能夠在這里找到?英語
Learn how to make sense of crash logs!
本文作者是?Soheil Moayedi Azarpour, 他是一名獨立iOS開發人員。
作為一名應用開發人員,你是否有過例如以下經歷?
為確保你的應用正確無誤,在將其提交到應用商店之前,你必然進行了大量的測試工作。它在你的設備上也執行得非常好,可是,上了應用商店后。還是實用戶抱怨會閃退 !
假設你跟我一樣是個完美主義者,你肯定想將應用做到盡善盡美。于是你打開代碼準備修復閃退的問題……可是,從何處著手呢?
這時iOS崩潰日志派上用場了。在大多數情況下。你能從中了解到關于閃退的詳盡、實用的信息。
通過本教程。你將學習到一些常見的崩潰日志案例,以及怎樣從開發設備和iTunes Connect上獲取崩潰日志文件。你還將學習到符號化( symbolication),從日志追蹤到代碼 。
你還將學習調試一個在待定情況下會閃退的應用。
讓我們開始動手吧!
什么是崩潰日志,從哪里能得它?
iOS設備上的應用閃退時。操作系統會生成一個崩潰報告。也叫崩潰日志。保存在設備上。
崩潰日志上有非常多實用的信息,包含應用是什么情況下閃退的。
通常。上面有每一個正在運行線程的完整堆棧跟蹤信息,所以你能從中了解到閃退發生時各線程都在做什么,并分辨出閃退發生在哪個線程上。
有幾種方法能夠從設備上獲取崩潰日志。
設備與電腦上的iTunes Store同步后,會將崩潰日志保存在電腦上。依據電腦操作系統的不同。崩潰日志將保存在下面位置:
Mac OS X:
| ~/Library/Logs/CrashReporter/MobileDevice/ |
Windows XP:
| C:Documents and Settings<USERNAME>Application DataApple ComputerLogsCrashReporterMobileDevice<DEVICE_NAME> |
Windows Vista or 7:
C:Users<USERNAME>AppDataRoamingApple ComputerLogsCrashReporterMobileDevice<DEVICE_NAME>當用戶抱怨閃退時,你能夠要求他讓設備與iTunes同步。并依據操作系統的不同,到上述位置把崩潰日志下載下來。然后通過電子郵件發送給你。
你必需盡量獲取用戶設備生成的全部崩潰日志。由于崩潰日志越多,就越easy診斷問題所在!
另外。假設你裝了Xcode,也能非常easy通過Xcode從你的設備上獲得崩潰日志。將iOS設備連接到電腦上,然后打開Xcode。
從菜單條上選擇?Window ?菜單, 然后選擇?Organizer?(快捷方式是 Shift-CMD-2).
在 Organizer 窗體上, 選中?Devices?標簽欄. 在左側的導航面板上。選中?Device Logs, 例如以下圖所看到的:
看看上圖。左側有好幾個 Device Logs 菜單項.?LIBRARY?以下的Device Logs是你全部設備(以前連接到Xcode的)的日志 。
每一個設備以下的 Device Logs 是相應設備的日志。
應用提交到App Store后,你也能從?iTunes Connect?獲取到用戶的崩潰日志. 登錄到 iTunes Connect 上, 選擇?Manage Your Applications, 點擊對應的應用, 點擊應用圖標以下的?View Details?button, 然后點擊右欄Links部分的 ?Crash Reports?。
假設沒有崩潰日志。試試點擊Refresh?button刷新一下。
假設你的應用還賣得不多。或者剛上架不久。iTunes Connect賬號上也可能還沒有不論什么崩潰日志。
假設iTunes Connect上有崩潰日志,你將看到例如以下圖:
有時。雖然實用戶報告閃退,你仍然看不到崩潰報告。這時,最好讓用戶直接把崩潰報告發送給你。
什么情況下會產生崩潰日志?
兩種主要情況會產生崩潰日志:
違反iOS規則包含在啟動、恢復、掛起、退出時watchdog超時、用戶強制退出和低內存終止。
讓我們具體了解一下吧。
Watchdog 超時機制
從iOS 4.x開始,退出應用時。應用不會馬上終止,而是退到后臺。
可是,假設你的應用響應不夠快,操作系統有可能會終止你的應用,并產生一個崩潰日志。
這些事件與下列UIApplicationDelegate方法相相應:
- application:didFinishLaunchingWithOptions:
- applicationWillResignActive:
- applicationDidEnterBackground:
- applicationWillEnterForeground:
- applicationDidBecomeActive:
- applicationWillTerminate:
上面全部這些方法。應用僅僅有有限的時間去完畢處理。假設花費時間太長,操作系統將終止應用。
注意:?假設你沒有把須要花費時間比較長的操作(如網絡訪問)放在后臺線程上就非常easy發生這樣的情況。關于假設避免這樣的情況的信息,請參考我們的另外兩篇教程:?Grand Central Dispatch?和?NSOperations。
用戶強制退出
iOS 4.x開始支持多任務。
假設應用堵塞界面并停止響應, 用戶能夠通過在主屏幕上雙擊Homebutton來終止應用。此時,操作應用將生成一個崩潰日志。
注意:?雙擊Homebutton后。你將看到執行過的全部應用。
那些應用不一定是正在執行,也不一定是被掛起。
通常,用戶點擊Homebutton時。應用將在后臺保留約10分鐘,然后操作系統自己主動將其終止。 所以雙擊Homebutton顯示的應用列表僅僅是表明那是一系列過去打開過的應用。刪除那些應用的圖標不會產生不論什么崩潰日志。
低內存終止
子類化UIViewController時,你也許已經注意到didReceiveMemoryWarning方法。
在前臺執行的應用擁有訪問和使用內存的最高優化級。然而。這并不意味著該應用能使用設備的全部可用內存 ——每一個應用僅僅能使用一部分可用內存。
當內存使用達到一定程度時,操作系統將發出一個?UIApplicationDidReceiveMemoryWarningNotification?通知。同一時候,調用?didReceiveMemoryWarning?方法。
此時,為了讓應用繼續正常執行。操作系統開始終止在后臺的其它應用以釋放一些內存。全部后臺應用被終止后,假設你的應用還須要很多其它內存,操作系統會將你的應用也終止掉,并產生一個崩潰日志。而在這樣的情況下被終止的后臺應用,不會產生崩潰日志。
注意:?依據?蘋果文檔, Xcode不會自己主動加入低內存日志。你必需手動獲取日志。 然而,依據我的個人經驗。使用 Xcode 4.5.2, 低內存日志也會自己主動導入,僅僅是”Process”和”Type”屬性都被標為Unknown(未知)。
另外,值得一提的是在極短時間內分配一大塊內存將給系統內存帶來巨大負擔。
這樣。也會產生內存警告的通知。
應用中有Bug
如你所想。大多數閃退都是由于應用中有Bug,因此大多數崩潰日志的產生都是由于應用中的Bug。Bug的種類的有非常多。
在本教程的后半部分,你將通過調試一個會產生崩潰日志的含有Bug的應用,學習怎樣找到問題所在并進行修復!
崩潰日志的實例
讓我們看看一個崩潰日志的實例,以使你在處理一些實際問題之前心里有譜。
事不宜遲,見見你的新朋友吧:
| // 1: 進程信息 Incident Identifier: 30E46451-53FD-4965-896A-457FC11AD05F CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31 Hardware Model: iPhone4,1 Process: Rage Masters [4155] Path: /var/mobile/Applications/A5635B22-F5EF-4CEB-94B6-FE158D885014/Rage Masters.app/Rage Masters Identifier: Rage Masters Version: ??? (???) Code Type: ARM (Native) Parent Process: launchd [1] // 2: 基本信息 Date/Time: 2012-10-17 21:39:06.967 -0400 OS Version: iOS 6.0 (10A403) Report Version: 104 // 3: 異常 Exception Type: 00000020 Exception Codes: 0x000000008badf00d Highlighted Thread: 0 // 4: 線程回溯 Thread 0 name: Dispatch queue: com.apple.main-thread Thread 0: 0 libsystem_kernel.dylib 0x327f2eb4 mach_msg_trap + 20 1 libsystem_kernel.dylib 0x327f3048 mach_msg + 36 2 CoreFoundation 0x36bd4040 __CFRunLoopServiceMachPort + 124 3 CoreFoundation 0x36bd2d9e __CFRunLoopRun + 878 4 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352 5 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100 6 CFNetwork 0x32ac343e CFURLConnectionSendSynchronousRequest + 330 7 Foundation 0x346e69ba +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 242 8 Rage Masters 0x000d4046 0xd2000 + 8262 Thread 1: 0 libsystem_kernel.dylib 0x32803d98 __workq_kernreturn + 8 1 libsystem_c.dylib 0x3a987cf6 _pthread_workq_return + 14 2 libsystem_c.dylib 0x3a987a12 _pthread_wqthread + 362 3 libsystem_c.dylib 0x3a9878a0 start_wqthread + 4 // 5: 線程狀態 Thread 0 crashed with ARM Thread State (32-bit):r0: 0x00000000 r1: 0x00000000 r2: 0x00000001 r3: 0x39529fc8r4: 0xffffffff r5: 0x2fd7d301 r6: 0x2fd7d300 r7: 0x2fd7d9d0r8: 0x2fd7d330 r9: 0x3adbf8a8 r10: 0x2fd7d308 r11: 0x00000032ip: 0x00000025 sp: 0x2fd7d2ec lr: 0x001bdb25 pc: 0x30301838cpsr: 0x00000010 // 6: 二進制映像 Binary Images: 0xd2000 - 0xd7fff +Rage Masters armv7 /var/mobile/Applications/A5635B22-F5EF-4CEB-94B6-FE158D885014/Rage Masters.app/Rage Masters 0x2fe41000 - 0x2fe61fff dyld armv7 /usr/lib/dyld 0x327f2000 - 0x32808fff libsystem_kernel.dylib armv7 /usr/lib/system/libsystem_kernel.dylib 0x328a8000 - 0x328bdfff libresolv.9.dylib armv7 /usr/lib/libresolv.9.dylib 0x32a70000 - 0x32b35fff CFNetwork armv7 /System/Library/Frameworks/CFNetwork.framework/CFNetwork 0x32b7a000 - 0x32cc3fff libicucore.A.dylib armv7 /usr/lib/libicucore.A.dylib 0x32cc4000 - 0x32cc5fff CoreSurface armv7 /System/Library/PrivateFrameworks/CoreSurface.framework/CoreSurface 0x32f65000 - 0x32f8afff OpenCL armv7 /System/Library/PrivateFrameworks/OpenCL.framework/OpenCL |
這報告看起來像天書。
:) 我們分幾部分來解讀吧:
(1) 進程信息
第一部分是閃退進程的相關信息。
- Incident Identifier是崩潰報告的唯一標識符。
- CrashReporter Key?是與設備標識相相應的唯一鍵值。盡管它不是真正的設備標識符,但也是一個很實用的情報:假設你看到100個崩潰日志的CrashReporter Key值都是同樣的。或者僅僅有少數幾個不同的CrashReport值,說明這不是一個普遍的問題,僅僅發生在一個或少數幾個設備上。
- Hardware Model?標識設備類型。 假設非常多崩潰日志都是來自同樣的設備類型。說明應用僅僅在某特定類型的設備上有問題。上面的日志里。崩潰日志產生的設備是iPhone 4s。
- Process?是應用名稱。中括號中面的數字是閃退時應用的進程ID。
- 接下來幾行不言自明,無需贅述。
(2) 基本信息
這部分給出了一些基本信息,包含閃退發生的日期和時間,設備的iOS版本號。假設有非常多崩潰日志都來自iOS 6.0。說明問題僅僅發生在iOS 6.0上。
(3) 異常
在這部分。你能夠看到閃退發生時拋出的異常類型。
還能看到異常編碼和拋出異常的線程。依據崩潰報告類型的不同。在這部分你還能看到一些另外的信息。
(4) 線程回溯
這部分提供應用中全部線程的回溯日志。?回溯是閃退發生時全部活動幀清單。它包括閃退發生時調用函數的清單。
看以下這行日志:
| 2 XYZLib 0x34648e88 0x83000 + 8740 |
它包含四列:
(5) 線程狀態
這部分是閃退時寄存器中的值。
一般不須要這部分的信息,由于回溯部分的信息已經足夠讓你找出問題所在。
(6) 二進制映像
這部分列出了閃退時已經載入的二進制文件。
符號化Symbolication
第一次看到崩潰日志上的回溯時。你也許會認為它沒什么意義。我們習慣用法名和行數,而非像這種神奇位置:
| 6 Rage Masters 0x0001625c 0x2a000 + 3003 |
將這些十六進制地址轉化成方法名稱和行數的過程稱之為符號化。
從Xcode的Organizer窗體獲取崩潰日志后過幾秒鐘。崩潰日志將被自己主動符號化。上面那行被符號化后的版本號例如以下 :
| 6 Rage Masters 0x0001625c -[RMAppDelegate application:didFinishLaunchingWithOptions:] (RMAppDelegate.m:35) |
Xcode符號化崩潰日志時,須要訪問與App Store上相應的應用二進制文件以及生成二進制文件時產生的?.dSYM?文件。必需全然匹配才行。否則,日志將無法被全然符號化。
所以,保留每一個分發給用戶的編譯版本號很重要。
提交應用前進行歸檔時,Xcode將保存應用的二進制文件。能夠在Xcode Organizer的Archives標簽欄下找到全部已歸檔的應用文件。
在發現崩潰日志時,假設有相匹配的.dSYM文件和應用二進制文件。Xcode會自己主動對崩潰日志進行符號化。假設你換到別的電腦或創建新的賬戶。務必將全部二進制文件移動到正確的位置,使Xcode能找到它們。
注意:?你必需同一時候保留應用二進制文件和.dSYM文件才干將崩潰日志完整符號化。每次提交到iTunes Connect的構建都必需歸檔。
.dSYM文件和二進制文件是特定綁定于每一次構建和興許構建的,即使來自同樣的源碼文件。每一次構建也與其它構建不同。不能相互替換。
假設你使用Build 和 Archive 命令,這些文件會自己主動放在適當位置。 假設不是使用Build 和 Archive命令,放在Spotlight可以搜索到的位置(比方Home文件夾)就可以。
?
低內存閃退
由于低內存崩潰日志與普通崩潰日志略有不同,所以本教程特別分開說明一下。
:]
iOS設備檢測到低內存時,虛擬內存系統發出通知請求應用釋放內存。這些通知發送到全部正在執行的應用和進程,試圖收回一些內存。
假設內存使用依舊居高不下。系統將會終止后臺線程以緩解內存壓力。
假設可用內存足夠,應用將可以繼續執行而不會產生崩潰報告。否則,應用將被iOS終止。并產生低內存崩潰報告。
低內存崩潰日志上沒有應用線程的堆棧回溯。相反,上面顯示的是以內存頁數為單位的各進程內存使用量。
(在撰寫本文的時候,一個內存頁的大小是4KB。)
被iOS因釋放內存頁終止的進程名稱后面你會看到jettisoned?字樣。假設看到它出如今你的應用名稱后面,說明你的應用因使用太多內存而被終止了。
低內存崩潰日志看起來像這樣:
| Incident Identifier: 30E46451-53FD-4965-896A-457FC11AD05F CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31 OS Version: iPhone OS 3.1.3 (7E18) Date/Time: 2012-10-17 21:39:06.967 -0400 Free pages: 96 Wired pages: 10558 Purgeable pages: 0 Largest process: Rage Masters ProcessesName UUID Count resident pagesRage Masters 9320 (jettisoned) (active)mediaserverd 255dataaccessd 505syslogd 71apsd 171securityd 243notifyd 2027CommCenter 189SpringBoard 2158 (active)accessoryd 91configd 371fairplayd 93mDNSResponder 292lockdownd 1204launchd 72 |
當應用發生低內存閃退時,你必需看看應用中內存使用的方式,以及是怎樣處理低內存警告的。
你能夠使用Instruments工具中使用Allocations 和 Leaks來發現內存分配問題和內存泄漏問題。
假設你不知道怎樣利用 Instruments 檢查內存問題,能夠看看這個教程?。
還有,別忘記虛擬內存!
Instruments工具的Leaks 和 Allocations 不能跟蹤顯存使用情況。必需使用 VM Tracker 才干查看顯存使用情況。
VM Tracker 默認是關閉的。打開Instrument,手動 選中Automatic Snapshotting?標志或者按下Snapshot Now?button。
本教程后面將會學習怎樣研究低內存崩潰日志。
異常編碼
在研究真實閃退場景之前,另一點須要重點介紹一下:就是那些有趣的異常編碼 。
你能夠在報告的異常部分——前面代碼的第3部分找到異常編碼。
有些編碼比較常見。
通常。異常編碼以一些文字開頭,緊接著是一個或多個十六進制值。此數值正是說明閃退根本性質的所在。 ?從這些編碼中,能夠區分出閃退是由于程序錯誤、非法內存訪問或者是其它原因。
以下是一些常見的異常編碼:
- 0x8badf00d: 讀做 “ate bad food”! (把數字換成字母。是不是非常像 :p)該編碼表示應用是由于發生watchdog超時而被iOS終止的。
?一般是應用花費太多時間而無法啟動、終止或響應用系統事件。
- 0xbad22222: 該編碼表示 VoIP 應用由于過于頻繁重新啟動而被終止。
- 0xdead10cc: 讀做 “dead lock”!該代碼表明應用由于在后臺執行時占用系統資源。如通訊錄數據庫不釋放而被終止 。
- 0xdeadfa11: 讀做 “dead fall”! 該代碼表示應用是被用戶強制退出的。
依據蘋果文檔, 強制退出發生在用戶長按開關button直到出現 “滑動來關機”, 然后長按 Homebutton。強制退出將產生 包括0xdeadfa11 異常編碼的崩潰日志, 由于大多數是強制退出是由于應用堵塞了界面。
?
注意:?在后臺任務列表中關閉已掛起的應用不會產生崩潰日志。
一旦應用被掛起,它何時被終止都是合理的。所以不會產生崩潰日志。
大展身手的時候到了!
好了! 你已經學習了全部分析崩潰日志和修復錯誤的基礎知識!
如果你剛進入Rage-O-Rage有限公司工作。
該公司有一個在App Store上熱銷的應用,叫 Rage Masters。
你的老板安迪要你幫忙解決幾個用戶常常抱怨閃退問題。你的任務就是研究這些閃退,符號化用戶提供的崩潰日志,查找問題所在,并修復之。
你能夠從?這里下載應用的源碼。
注意: 假設你想自己又一次生成崩潰報告,請遵照下面指引:
?
場景 1: 糟糕的代碼
一封來自用戶的郵件: “大哥,你的應用就是一坨屎! 我將其下載到我自己的iPod Touch和iPhone上。還下載到我兒子的iPod Touch上。在全部的設備上,都是還沒打開就閃退了……”
別一封來自用戶的郵件說, “我下載了你們的應用,一打開就閃退。
真悲催…”
還有一封郵件說得更明白:”你們的應用不能執行。我把它下載到我和妻子的設備上。全部設備都是 一打開就閃退了…”
好吧,別灰心! 這些意見藏著什么玄機呢?讓我們看看崩潰日志吧:
| Incident Identifier: 85833DBA-3DF7-43EE-AF80-4E5C51091F42
CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model: iPhone4,1
Process: Rage Masters [20067]
Path: /var/mobile/Applications/B2121A89-3D1F-4E61-BB18-5511E1DC150F/Rage Masters.app/Rage Masters
Identifier: Rage Masters
Version: ?? ? (? ??) Code Type: ARM (Native) Parent Process: launchd [1] Date/Time: 2012-11-03 13:37:31.148 -0400 OS Version: iOS 6.0 (10A403) Report Version: 104 Exception Type: 00000020 Exception Codes: 0x000000008badf00d Highlighted Thread: 0 Application Specific Information: Soheil-Azarpour.Rage-Masters failed to launch in time Elapsed total CPU time (seconds): 8.030 (user 8.030, system 0.000), 20% CPU Elapsed application CPU time (seconds): 3.840, 10% CPU Thread 0 name: Dispatch queue: com.apple.main-thread Thread 0: 0 libsystem_kernel.dylib 0x327f2eb4 mach_msg_trap + 20 1 libsystem_kernel.dylib 0x327f3048 mach_msg + 36 2 CoreFoundation 0x36bd4040 __CFRunLoopServiceMachPort + 124 3 CoreFoundation 0x36bd2d9e __CFRunLoopRun + 878 4 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352 5 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100 6 CFNetwork 0x32ac343e CFURLConnectionSendSynchronousRequest + 330 7 Foundation 0x346e69ba +[NSURLConnection sendSynchronousRequest:returningResponse:error:] + 242 8 Rage Masters 0x000ea1c4 -[RMAppDelegate application:didFinishLaunchingWithOptions:] (RMAppDelegate.m:36) 9 UIKit 0x37f30ad4 -[UIApplication _handleDelegateCallbacksWithOptions:isSuspended:restoreState:] + 248 10 UIKit 0x37f3065e -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 1186 11 UIKit 0x37f28846 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 694 12 UIKit 0x37ed0c3c -[UIApplication handleEvent:withNewEvent:] + 1000 13 UIKit 0x37ed06d0 -[UIApplication sendEvent:] + 68 14 UIKit 0x37ed011e _UIApplicationHandleEvent + 6150 15 GraphicsServices 0x370835a0 _PurpleEventCallback + 588 16 GraphicsServices 0x370831ce PurpleEventCallback + 30 17 CoreFoundation 0x36bd4170 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 32 18 CoreFoundation 0x36bd4112 __CFRunLoopDoSource1 + 134 19 CoreFoundation 0x36bd2f94 __CFRunLoopRun + 1380 20 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352 21 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100 22 UIKit 0x37f27480 -[UIApplication _run] + 664 23 UIKit 0x37f242fc UIApplicationMain + 1116 24 Rage Masters 0x000ea004 main (main.m:16) 25 libdyld.dylib 0x3b630b1c start + 0 |
發現問題了嗎?
異常編碼是0x000000008badf00d,還有后面的報告:
| Application Specific Information: Soheil-Azarpour.Rage-Masters failed to launch in time Elapsed total CPU time (seconds): 8.030 (user 8.030, system 0.000), 20% CPU Elapsed application CPU time (seconds): 3.840, 10% CPU |
這說明應用在啟動時就閃退了。iOS的watchdog機制終止了應用。帥! 找到問題了,可是為什會發生這種事呢?
接著往下看日志。 從下向上讀回溯日志。
最底下的幀 (frame 25: libdyld.dylib)是最先調用的,然后是幀24, Rage Masters, main (main.m:16) ,依此類推。
跟應用源碼相關的幀是最重要的。忽略掉系統庫和框架。下一個與代碼相關的幀是:
| 8 Rage Masters 0x0009f244 -[RMAppDelegate application:didFinishLaunchingWithOptions:] (RMAppDelegate.m:35) |
應用在運行RMAppDelegate (RMAppDelegate.m:35)類application:didFinishLaunchingWithOptions:?方法第35 行代碼時閃退。
打開Xcode看看那行代碼:
| NSData *directoryData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil]; |
就是它了! 同步調用web服務?! 在主線程上?
! 在?application:didFinishLaunchingWithOptions: 方法上?!! 誰寫的代碼呀?!
Network calls on the main thread makes kittens sad.
無論怎樣,問題得你來修復了。這個調用必需異步進行,甚至更理想的情況是,在application:didFinishLaunchingWithOptions:返回YES之后的其它部分再運行Web服務。
在其它地方調用可能須要比較多的改動。
當下,我們僅僅要使應用不閃退即可。
能夠在日后再實現更好的設計。
將上面那行討厭的代碼(及其以下的三行代碼)換成以下這個異步的版本號吧:
| [NSURLConnection sendAsynchronousRequest:requestqueue:[NSOperationQueue mainQueue]completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){NSURL *cacheDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSUserDirectory inDomains:NSCachesDirectory] lastObject];NSURL *filePath = [NSURL URLWithString:kDirectoryFile relativeToURL:cacheDirectory];[data writeToFile:[filePath absoluteString] atomically:YES];}]; |
?
場景 2: 無法響應事件的button
一名用戶說: “我不能將某個rage master加入到書簽里面。我想加入的時候應用就閃退…”
用一名用戶說 :”書簽不能用 … 在具體頁面上,點擊書簽button,應用就閃退了!”
上面的抱怨說得不是非常清楚,引起問題的解決辦法肯定有多樣。看看崩潰日志:
| Incident Identifier: 3AAA63CC-3088-41CC-84D9-82FE03F9F354
CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model: iPhone4,1
Process: Rage Masters [20090]
Path: /var/mobile/Applications/B2121A89-3D1F-4E61-BB18-5511E1DC150F/Rage Masters.app/Rage Masters
Identifier: Rage Masters
Version: ?? ? (???) Code Type: ARM (Native) Parent Process: launchd [1] Date/Time: 2012-11-03 13:39:00.081 -0400 OS Version: iOS 6.0 (10A403) Report Version: 104 Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Crashed Thread: 0 Last Exception Backtrace: 0 CoreFoundation 0x36bff29e __exceptionPreprocess + 158 1 libobjc.A.dylib 0x34f0f97a objc_exception_throw + 26 2 CoreFoundation 0x36c02e02 -[NSObject(NSObject) doesNotRecognizeSelector:] + 166 3 CoreFoundation 0x36c0152c ___forwarding___ + 388 4 CoreFoundation 0x36b58f64 _CF_forwarding_prep_0 + 20 5 UIKit 0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68 6 UIKit 0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26 7 UIKit 0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40 8 UIKit 0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498 9 UIKit 0x37fbade4 -[UIControl touchesEnded:withEvent:] + 484 10 UIKit 0x37ee35f4 -[UIWindow _sendTouchesForEvent:] + 520 11 UIKit 0x37ed0804 -[UIApplication sendEvent:] + 376 12 UIKit 0x37ed011e _UIApplicationHandleEvent + 6150 13 GraphicsServices 0x3708359e _PurpleEventCallback + 586 14 GraphicsServices 0x370831ce PurpleEventCallback + 30 15 CoreFoundation 0x36bd416e __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 30 16 CoreFoundation 0x36bd4112 __CFRunLoopDoSource1 + 134 17 CoreFoundation 0x36bd2f94 __CFRunLoopRun + 1380 18 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352 19 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100 20 GraphicsServices 0x370822e6 GSEventRunModal + 70 21 UIKit 0x37f242fc UIApplicationMain + 1116 22 Rage Masters 0x000ca004 main (main.m:16) 23 libdyld.dylib 0x3b630b1c start + 0 |
異常代碼是SIGABRT。通常, ?SIGABRT 異常是因為某個對象接收到未實現的消息引起的。 或者。用簡單的話說,在某個對象上調用了不存在的方法。
這樣的情況一般不會發生。由于A對象調用了B方法。假設B方法不存在,編譯器會報錯。可是。假設你是使用selector間接調用方法的。編譯器則無法檢測對象是否存在該方法了。
回到崩潰日志。它指出閃退發生在編號為0的線程上。 這意味著非常可能是在主線程上調用了某個對象沒有實現的方法。
假設你接著閱讀回溯日志,會發現跟你的代碼相關的僅僅有幀22, main.m:16. 這沒有多大幫助。 :[
繼續向上查看框架調用。出現這個:
| 2 CoreFoundation 0x36c02e02 -[NSObject(NSObject) doesNotRecognizeSelector:] + 166 |
這不是你自己寫的代碼。但至少它確認了是對象調用了一個沒有實現的方法。
回到RMDetailViewController.m文件, 由于那是書簽button實現動作的地方。 找到書簽功能代碼:
| -(IBAction)bookmarkButtonPressed {self.master.isBookmarked = !self.master.isBookmarked;// Update shared bookmarksif (self.master.isBookmarked)[[RMBookmarks sharedBookmarks] bookmarkMaster:self.master];else[[RMBookmarks sharedBookmarks] unbookmarkMaster:self.master];// Update UI[self updateBookmarkImage]; } |
看起來沒什么問題,再檢查一下storyboard (XIB文件) 。確認button連接的正確性。
就是它了! 在?MainStoryboard.storyboard,button連接的是 bookmarkButtonPressed: 而不是bookmarkButtonPressed (注意后面的分號說明方法有一個參數)。 僅僅要將上面的方法簽名改動成這樣就能修復問題了:
| -(IBAction)bookmarkButtonPressed:(id)sender {// Remain unchanged.. } |
當然,你也能夠簡單地在XIB文件上刪除錯誤的連接,然后又一次連接方法,使XIB文件連接到正確的方法上。
兩者方法都行。
又處理了一個閃退問題。好樣的。:]
場景 3: 表格上的Bug
還有一用戶抱怨道, “在書簽視圖上無法刪除書簽…” 還有還有一用戶抱怨相同的問題, “當我試圖刪除書簽時,應用閃退…”
這些郵件沒什么作用,還是看看崩潰日志!
| Incident Identifier: 5B62D681-D8FE-41FE-8D52-AB7E6D6B2AC7
CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31
Hardware Model: iPhone4,1
Process: Rage Masters [20088]
Path: /var/mobile/Applications/B2121A89-3D1F-4E61-BB18-5511E1DC150F/Rage Masters.app/Rage Masters
Identifier: Rage Masters
Version: ? ?? (? ??) Code Type: ARM (Native) Parent Process: launchd [1] Date/Time: 2012-11-03 13:38:45.762 -0400 OS Version: iOS 6.0 (10A403) Report Version: 104 Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Crashed Thread: 0 Last Exception Backtrace: 0 CoreFoundation 0x36bff29e __exceptionPreprocess + 158 1 libobjc.A.dylib 0x34f0f97a objc_exception_throw + 26 2 CoreFoundation 0x36bff158 +[NSException raise:format:arguments:] + 96 3 Foundation 0x346812aa -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 86 4 UIKit 0x37f04b7e -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] + 7690 5 UIKit 0x3803a4a2 -[UITableView deleteRowsAtIndexPaths:withRowAnimation:] + 22 6 Rage Masters 0x000fd9ca -[RMBookmarksViewController tableView:commitEditingStyle:forRowAtIndexPath:] (RMBookmarksViewController.m:68) 7 UIKit 0x3809a5d4 -[UITableView(UITableViewInternal) animateDeletionOfRowWithCell:] + 80 8 UIKit 0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68 9 UIKit 0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26 10 UIKit 0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40 11 UIKit 0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498 12 UIKit 0x37fbb0a8 -[UIApplication sendAction:to:from:forEvent:] + 68 13 UIKit 0x37fbb05a -[UIApplication sendAction:toTarget:fromSender:forEvent:] + 26 14 UIKit 0x37fbb038 -[UIControl sendAction:to:forEvent:] + 40 15 UIKit 0x37fba8ee -[UIControl(Internal) _sendActionsForEvents:withEvent:] + 498 16 UIKit 0x37fbade4 -[UIControl touchesEnded:withEvent:] + 484 17 UIKit 0x37ee35f4 -[UIWindow _sendTouchesForEvent:] + 520 18 UIKit 0x37ed0804 -[UIApplication sendEvent:] + 376 19 UIKit 0x37ed011e _UIApplicationHandleEvent + 6150 20 GraphicsServices 0x3708359e _PurpleEventCallback + 586 21 GraphicsServices 0x370831ce PurpleEventCallback + 30 22 CoreFoundation 0x36bd416e __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 30 23 CoreFoundation 0x36bd4112 __CFRunLoopDoSource1 + 134 24 CoreFoundation 0x36bd2f94 __CFRunLoopRun + 1380 25 CoreFoundation 0x36b45eb8 CFRunLoopRunSpecific + 352 26 CoreFoundation 0x36b45d44 CFRunLoopRunInMode + 100 27 GraphicsServices 0x370822e6 GSEventRunModal + 70 28 UIKit 0x37f242fc UIApplicationMain + 1116 29 Rage Masters 0x000fb004 main (main.m:16) 30 libdyld.dylib 0x3b630b1c start + 0 |
這看起來跟前面那個崩潰日志非常像。是還有一個SIGABRT 異常。 你可能想知道是否是同樣的問題:發送信息到一個沒有實現對應方法的對象?
讓我們從回溯日志看看哪些方法被調用了。從底部開始,你的源碼最后被調用的是幀 6:
| 6 Rage Masters 0x00088c66 -[RMBookmarksViewController tableView:commitEditingStyle:forRowAtIndexPath:] (RMBookmarksViewController.m:68) |
這是UITableViewDataSource 的一個方法. 呵呵?! 毫無疑問蘋果已經實現了該方法 —— 你能夠重載它, 但不像是還沒有實現。并且,這是個可選的委派方法。 所以問題不是調用了一個沒有實現的方法。
再看看上面的幾個幀:
| 3 Foundation 0x346812aa -[NSAssertionHandler handleFailureInMethod:object:file:lineNumber:description:] + 86 4 UIKit 0x37f04b7e -[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:] + 7690 5 UIKit 0x3803a4a2 -[UITableView deleteRowsAtIndexPaths:withRowAnimation:] + 22 |
幀 5, UITableView調用了它自己的還有一個方法?deleteRowsAtIndexPaths:withRowAnimation:?然后是看起來像蘋果內部方法的_endCellAnimationsWithContext:?被調用。
然后Foundation framework發生異常handleFailureInMethod:object:file:lineNumber:description:.
這些分析結合用戶的抱怨,看起來是你在處理UITableView刪除行過程中有Bug。回到Xcode。你知道看哪里嗎 ? 能從崩潰日志中推斷出來?
就是RMBookmarksViewController.m文件的第68行:
| - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } |
發現問題了嗎? 給你點時間,細致看一下。
找到了吧! 數據源呢?
代碼在表格視圖上刪除了一行。但并沒有改動背后的數據源。把上面的代碼替換成以下的就能修復問題了:
| -(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {RMMaster *masterToDelete = [bookmarks objectAtIndex:indexPath.row];[bookmarks removeObject:masterToDelete];[[RMBookmarks sharedBookmarks] unbookmarkMaster:masterToDelete];[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } |
搞定了!走起,討厭的 bug!!
這篇文章還能夠在這里找到?英語
場景 4: 吃棒棒糖時閃退!
用戶郵件說, “當rage master吃棒棒糖時應用就閃退…” 還有一用戶說, “我讓rage master 吃棒棒糖。沒幾次應用就閃退了!”
崩潰日志例如以下:
| Incident Identifier: 081E58F5-95A8-404D-947B-5E104B6BC1B1 CrashReporter Key: 5a56599d836c4f867f6eec76afee451bf9ae5f31 Hardware Model: iPhone4,1 OS Version: iPhone OS 6.0 (10A403) Kernel Version: Darwin Kernel Version 13.0.0: Sun Aug 19 00:28:05 PDT 2012; root:xnu-2107.2.33~4/RELEASE_ARM_S5L8940X Date: 2012-11-03 13:39:59 -0400 Time since snapshot: 4353 ms Free pages: 968 Active pages: 7778 Inactive pages: 4005 Throttled pages: 92319 Purgeable pages: 0 Wired pages: 23347 Largest process: Rage Masters |
| ProcessesName <UUID> rpages recent_max [reason] (state)lsd <6a9f5b5f36b23fc78f87b6d8f1f49a9d> 331 331 [vm] (daemon) (idle)afcd <b0aff2e7952e34a9882fec81a8dcdbb2> 141 141 [vm] (daemon) (idle)itunesstored <4e0cd9f873de3435b4119c48b2d6d13d> 1761 1761 [vm] (daemon) (idle) softwareupdatese <2bc4b5ae016431c98d3b34f81027d0ae> 311 311 [vm] (daemon) (idle)Amazon <4600481f07ec3e59a925319b7f67ba14> 2951 2951 [vm] (suspended)accountsd <ac0fce15c1a2350d951efc498d521ac7> 519 519 [vm] (daemon) (idle) coresymbolicatio <edba67001f76313b992056c712153b4b> 126 126 [vm] (daemon) (idle)Skype <504cf2fe60cb3cdea8273e74df09836b> 3187 3187 [vm] (background)MobileMail <bff817c61ce33c85a43ea9a6c98c29f5> 14927 14927 [vm] (continuous)MobileSMS <46778de076363d67aeea207464cfc581> 2134 2134 [vm] (background)MobilePhone <3fca241f2a193d0fb8264218d296ea41> 2689 2689 [vm] (continuous)librariand <c9a9be81aa9632f0a913ce79b911f27e> 317 317 [vm] (daemon)kbd <3e7136ddcefc3d77a01499db593466cd> 616 616 [vm] (daemon)tccd <eb5ddcf533663f8d987d67cae6a4c4ea> 224 224 [vm] (daemon)Rage Masters <90b45d6281e934209c5b06cf7dc4d492> 28591 28591 [vm] (frontmost) (resume)ptpd <04a56fce67053c57a7979aeea8e5a7ea> 879 879 (daemon)iaptransportd <f784f30dc09d32078d87b450e8113ef6> 230 230 (daemon)locationd <892cd1c9ffa43c99a82dba197be5f09e> 1641 1641 (daemon)syslogd <cbef142fa0a839f0885afb693fb169c3> 237 237 (daemon)mediaserverd <80657170daca32c9b8f3a6b1faac43a2> 4869 4869 (daemon)dataaccessd <2a3f6a518f3f3646bf35eddd36f25005> 1786 1786 (daemon)aosnotifyd <d4d14f2914c3343796e447cfef3e6542> 549 549 (daemon)wifid <9472b090746237998cdbb9b34f090d0c> 455 455 (daemon)SpringBoard <27372aae101f3bbc87804edc10314af3> 18749 18749 backboardd <5037235f295b33eda98eb5c72c098858> 5801 5801 (daemon)UserEventAgent <6edfd8d8dba23187b05772dcdfc94f90> 601 601 (daemon)mediaremoted <4ff39c50c684302492e396ace813cb25> 293 293 (daemon)pasteboardd <8a4279b78e4a321f84a076a711dc1c51> 176 176 (daemon) springboardservi <ff6f64b3a21a39c9a1793321eefa5304> 0 0 (daemon)syslog_relay <45e9844605d737a08368b5215bb54426> 0 0 (daemon)DTMobileIS <23303ca402aa3705870b01a9047854ea> 0 0 (daemon) notification_pro <845b7beebc8538ca9ceef731031983b7> 169 169 (daemon)syslog_relay <45e9844605d737a08368b5215bb54426> 0 0 (daemon)ubd <74dc476d1785300e9fcda555fcb8d774> 976 976 (daemon)twitterd <4b4946378a9c397d8250965d17055b8e> 730 730 (daemon)configd <4245d73a9e96360399452cf6b8671844> 809 809 (daemon)absinthed.N94 <7f4164c844fa340caa940b863c901aa9> 99 99 (daemon) filecoordination <fbab576f37a63b56a1039153fc1aa7d8> 226 226 (daemon)distnoted <a89af76ec8633ac2bbe99bc2b7964bb0> 137 137 (daemon)apsd <94d8051dd5f5362f82d775bc279ae608> 373 373 (daemon)networkd <0032f46009f53a6c80973fe153d1a588> 219 219 (daemon)aggregated <8c3c991dc4153bc38aee1e841864d088> 112 112 (daemon)BTServer <c92fbd7488e63be99ec9dbd05824f5e5> 522 522 (daemon)fairplayd.N94 <7bd896bd00783a48906090d05cf1c86a> 210 210 (daemon)fseventsd <996cc4ca03793184aea8d781b55bce08> 384 384 (daemon)imagent <1e68080947be352590ce96b7a1d07b2f> 586 586 (daemon)mDNSResponder <3e557693f3073697a58da6d27a827d97> 295 295 (daemon)lockdownd <ba1358c7a8003f1b91af7d5f58dd5bbe> 389 389 (daemon)powerd <2d2ffed5e69638aeba1b92ef124ed861> 174 174 (daemon)CommCenter <1f425e1e897d32e8864fdd8eeaa803a8> 2212 2212 (daemon)notifyd <51c0e03da8a93ac8a595442fcaac531f> 211 211 (daemon)ReportCrash <8c32f231b2ed360bb151b2563bcaa363> 337 337 (daemon) |
這日志跟我們前面見到的相差非常多。
這個一個來自iOS 6的低內存崩潰日志。
正如我們前面所說的,低內存崩潰日志與其它類型的崩潰日志非常不一樣,它們不指向特定的文件和代碼行。
相反,它們畫出了閃退時設備上的內存使用情況的圖表。
至少,頭部還是跟其它崩潰日志非常像的: ?提供了 Incident Identifier, CrashReporter Key, Hardware Model, OS Version等信息。
接下來部分是低內存崩潰日志特有的:
- Free pages?指可用內存頁數。每頁大小約是4KB, 上面的日志中,可用內存約為3,872 KB (或者說 3.9 MB)。
- Purgeable pages?是那部分可被清除或重用的內存。在上面的日志中,是0KB。
- Largest process是閃退時使用大部分內存的應用名稱。在上面的日志中,正是你的應用!
- Processes顯示了閃退時各進程列表,還包括內存使用量。
包括進程名 (第一列), 進程唯一標識符(第二名), 進程使用的內存頁數(第三列)。最后一列是每一個應用的狀態。
通常,發生閃退的應用的狀態是 frontmost。 這里是 Rage Masters, 使用28591 頁 (or 114.364 MB) 內存——這內存太多了!
通過,最大進程和frontmost狀態的應用是同樣的, 并且也是引起低內存閃退的應用進程。
可是也可能看到最大進程和 frontmost狀態應用不同的樣例。比方,假設最大進程是SpringBoard, 忽略它 , 由于 SpringBoard 進程是顯示主屏幕的應用。出如今你雙擊homebutton等情況,并且它是一直活動的。
低內存發生時,iOS向活動的應用發出低內存警告并終止后臺應用。假設前臺應用仍然繼續增長內存,iOS將終止它。
為了查找低內存問題的解決辦法,你必需使用Instruments剖析應用。假設你不知道怎么做,能夠看一下我們 一篇關于這個方面的教程.。 :] 另外, 你也能夠走捷徑。響應低內存警告通知,以解決部分閃退問題。
回到Xcode查看RMLollipopLicker.m文件。 這是實現吃棒棒糖的視圖控制器。看看源碼:
| #import "RMLollipopLicker.h"#define COUNT 20@interface RMLollipopLicker () @property (weak, nonatomic) IBOutlet UIProgressView *progressView; @property (weak, nonatomic) IBOutlet UILabel *label; @property (weak, nonatomic) IBOutlet UILabel *lickedTimeLabel; @end@implementation RMLollipopLicker { NSOperationQueue *queue; NSMutableArray *lollipops; }#pragma mark - Life cycle- (void)viewDidLoad { [super viewDidLoad];self.progressView.progress = 0.0; self.label.text = [NSString stringWithFormat:@"Tap on run and I'll lick a lollipop %d times!", COUNT]; self.lickedTimeLabel.text = @"";lollipops = [[NSMutableArray alloc] init]; queue = [[NSOperationQueue alloc] init]; }- (void)lickLollipop { NSURL *fileURL = [[NSBundle mainBundle] URLForResource:@"Lollipop" withExtension:@"plist"]; NSDictionary *dictionary = [NSDictionary dictionaryWithContentsOfURL:fileURL]; NSString *lollipop = [dictionary objectForKey:@"Lollipop"]; [lollipops addObject:lollipop]; }#pragma mark - IBActions- (IBAction)doneButtonPressed:(id)sender {[self dismissViewControllerAnimated:YES completion:nil]; }- (IBAction)runButtonPressed:(id)sender {[sender setEnabled:NO]; [queue addOperationWithBlock:^{for (NSInteger i = 0 ; i = COUNT) { self.label.text = [NSString stringWithFormat:@"Tap on run and I'll lick a lollipop %d times!", COUNT]; self.progressView.progress = 0.0; [sender setEnabled:YES]; } }]; } }];}@end |
當用戶點擊執行button, 應用開始一個背景線程。調用 lickLollipop 方法若干次,然后更新界面反映吃棒棒糖的數量。 lickLollipop 方法從屬性列表文件(PLIST)文件讀取一個長字符串,然后加入到數組上。這些數據并不重要, 能在不影響用戶體驗的前提下又一次創建。
利用每種可以清除和重建數據而不影響用戶體驗的情況是好習慣。
這樣可以方便地釋放內存,降低低內存警告。
那么,怎樣提高代碼質量呢? 實現 didReceiveMemoryWarning 方法。像以下這樣處理數據:
| -(void)didReceiveMemoryWarning { [lollipops removeAllObjects]; [super didReceiveMemoryWarning]; } |
搞定!
下一步?
萬歲,你研究了4個閃退案例! 你的應用更完好了,而且學到了一些重要的調試技巧。
你能夠到這里下載改進后的項目代碼。
轉載于:https://www.cnblogs.com/lytwajue/p/7183726.html
總結
以上是生活随笔為你收集整理的iOS应用崩溃日志分析 iOS应用崩溃日志揭秘的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: lintcode-102-带环链表
- 下一篇: OpenCV中findContours函