這篇文章還可以在這裏找到 英語ios
Learn how to make sense of crash logs!web
本文做者是 Soheil Moayedi Azarpour, 他是一名獨立iOS開發人員。spring
做爲一名應用開發人員,你是否有過例如如下經歷?
爲確保你的應用正確無誤,在將其提交到應用商店以前,你必然進行了大量的測試工做。它在你的設備上也執行得很是好,但是,上了應用商店後。仍是實用戶抱怨會閃退 !
假設你跟我同樣是個完美主義者,你確定想將應用作到盡善盡美。因而你打開代碼準備修復閃退的問題……但是,從何處着手呢?
這時iOS崩潰日誌派上用場了。在大多數狀況下。你能從中瞭解到關於閃退的詳盡、實用的信息。
經過本教程。你將學習到一些常見的崩潰日誌案例,以及怎樣從開發設備和iTunes Connect上獲取崩潰日誌文件。你還將學習到符號化( symbolication),從日誌追蹤到代碼 。數據庫
你還將學習調試一個在待定狀況下會閃退的應用。數組
讓咱們開始動手吧!網絡
iOS設備上的應用閃退時。操做系統會生成一個崩潰報告。也叫崩潰日誌。保存在設備上。
崩潰日誌上有很是多實用的信息,包含應用是什麼狀況下閃退的。app
一般。上面有每個正在運行線程的完整堆棧跟蹤信息,因此你能從中瞭解到閃退發生時各線程都在作什麼,並分辨出閃退發生在哪一個線程上。
有幾種方法可以從設備上獲取崩潰日誌。框架
設備與電腦上的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方法相相應:
上面所有這些方法。應用僅僅有有限的時間去完畢處理。假設花費時間太長,操做系統將終止應用。
注意: 假設你沒有把需要花費時間比較長的操做(如網絡訪問)放在後臺線程上就很是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: 0x39529fc8 r4: 0xffffffff r5: 0x2fd7d301 r6: 0x2fd7d300 r7: 0x2fd7d9d0 r8: 0x2fd7d330 r9: 0x3adbf8a8 r10: 0x2fd7d308 r11: 0x00000032 ip: 0x00000025 sp: 0x2fd7d2ec lr: 0x001bdb25 pc: 0x30301838 cpsr: 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) 進程信息
第一部分是閃退進程的相關信息。
(2) 基本信息
這部分給出了一些基本信息,包含閃退發生的日期和時間,設備的iOS版本號。假設有很是多崩潰日誌都來自iOS 6.0。說明問題僅僅發生在iOS 6.0上。
(3) 異常
在這部分。你可以看到閃退發生時拋出的異常類型。
還能看到異常編碼和拋出異常的線程。依據崩潰報告類型的不一樣。在這部分你還能看到一些另外的信息。
(4) 線程回溯
這部分提供應用中所有線程的回溯日誌。 回溯是閃退發生時所有活動幀清單。它包括閃退發生時調用函數的清單。
看如下這行日誌:
2 XYZLib 0x34648e88 0x83000 + 8740 |
它包含四列:
(5) 線程狀態
這部分是閃退時寄存器中的值。
通常不需要這部分的信息,因爲回溯部分的信息已經足夠讓你找出問題所在。
(6) 二進制映像
這部分列出了閃退時已經載入的二進制文件。
第一次看到崩潰日誌上的回溯時。你也許會認爲它沒什麼意義。咱們習慣用法名和行數,而非像這種神奇位置:
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 Processes Name UUID Count resident pages Rage Masters 9320 (jettisoned) (active) mediaserverd 255 dataaccessd 505 syslogd 71 apsd 171 securityd 243 notifyd 2027 CommCenter 189 SpringBoard 2158 (active) accessoryd 91 configd 371 fairplayd 93 mDNSResponder 292 lockdownd 1204 launchd 72 |
當應用發生低內存閃退時,你必需看看應用中內存使用的方式,以及是怎樣處理低內存警告的。
你可以使用Instruments工具中使用Allocations 和 Leaks來發現內存分配問題和內存泄漏問題。
假設你不知道怎樣利用 Instruments 檢查內存問題,可以看看這個教程 。
還有,別忘記虛擬內存!
Instruments工具的Leaks 和 Allocations 不能跟蹤顯存使用狀況。必需使用 VM Tracker 才幹查看顯存使用狀況。
VM Tracker 默認是關閉的。打開Instrument,手動 選中Automatic Snapshotting 標誌或者按下Snapshot Now button。
本教程後面將會學習怎樣研究低內存崩潰日誌。
在研究真實閃退場景以前,另外一點需要重點介紹一下:就是那些有趣的異常編碼 。
你可以在報告的異常部分——前面代碼的第3部分找到異常編碼。
有些編碼比較常見。
一般。異常編碼以一些文字開頭,緊接着是一個或多個十六進制值。此數值正是說明閃退根本性質的所在。 從這些編碼中,可以區分出閃退是因爲程序錯誤、非法內存訪問或者是其它緣由。
如下是一些常見的異常編碼:
通常是應用花費太多時間而沒法啓動、終止或響應用系統事件。
依據蘋果文檔, 強制退出發生在用戶長按開關button直到出現 「滑動來關機」, 而後長按 Homebutton。強制退出將產生 包括0xdeadfa11 異常編碼的崩潰日誌, 因爲大多數是強制退出是因爲應用堵塞了界面。
注意: 在後臺任務列表中關閉已掛起的應用不會產生崩潰日誌。
一旦應用被掛起,它什麼時候被終止都是合理的。因此不會產生崩潰日誌。
好了! 你已經學習了所有分析崩潰日誌和修復錯誤的基礎知識!
若是你剛進入Rage-O-Rage有限公司工做。
該公司有一個在App Store上熱銷的應用,叫 Rage Masters。
你的老闆安迪要你幫忙解決幾個用戶經常抱怨閃退問題。你的任務就是研究這些閃退,符號化用戶提供的崩潰日誌,查找問題所在,並修復之。
你可以從 這裏下載應用的源碼。
注意: 假設你想本身又一次生成崩潰報告,請遵守下面指引:
一封來自用戶的郵件: 「大哥,你的應用就是一坨屎! 我將其下載到我本身的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: ?? |
發現問題了嗎?
異常編碼是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: 方法上?!! 誰寫的代碼呀?!
不管怎樣,問題得你來修復了。這個調用必需異步進行,甚至更理想的狀況是,在application:didFinishLaunchingWithOptions:返回YES以後的其它部分再運行Web服務。
在其它地方調用可能需要比較多的改動。
當下,咱們僅僅要使應用不閃退便可。
可以在往後再實現更好的設計。
將上面那行討厭的代碼(及其如下的三行代碼)換成如下這個異步的版本號吧:
[NSURLConnection sendAsynchronousRequest:request queue:[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]; }]; |
一名用戶說: 「我不能將某個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: ?? |
異常代碼是SIGABRT。一般, SIGABRT 異常是由於某個對象接收到未實現的消息引發的。 或者。用簡單的話說,在某個對象上調用了不存在的方法。
這樣的狀況通常不會發生。因爲A對象調用了B方法。假設B方法不存在,編譯器會報錯。但是。假設你是使用selector間接調用方法的。編譯器則沒法檢測對象是否存在該方法了。
回到崩潰日誌。它指出閃退發生在編號爲0的線程上。 這意味着很是多是在主線程上調用了某個對象沒有實現的方法。
假設你接着閱讀回溯日誌,會發現跟你的代碼相關的僅僅有幀22, main.m:16. 這沒有多大幫助。 :[
繼續向上查看框架調用。出現這個:
這不是你本身寫的代碼。但至少它確認了是對象調用了一個沒有實現的方法。
回到RMDetailViewController.m文件, 因爲那是書籤button實現動做的地方。 找到書籤功能代碼:
-(IBAction)bookmarkButtonPressed { self.master.isBookmarked = !self.master.isBookmarked; // Update shared bookmarks if (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文件鏈接到正確的方法上。
二者方法都行。
又處理了一個閃退問題。好樣的。:]
還有一用戶抱怨道, 「在書籤視圖上沒法刪除書籤…」 還有還有一用戶抱怨相同的問題, 「當我試圖刪除書籤時,應用閃退…」
這些郵件沒什麼做用,仍是看看崩潰日誌!
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: ? |
這看起來跟前面那個崩潰日誌很是像。是還有一個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!!
這篇文章還可以在這裏找到 英語
用戶郵件說, 「當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 |
Processes Name <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等信息。
接下來部分是低內存崩潰日誌特有的:
包括進程名 (第一列), 進程惟一標識符(第二名), 進程使用的內存頁數(第三列)。最後一列是每個應用的狀態。
一般,發生閃退的應用的狀態是 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個閃退案例! 你的應用更無缺了,並且學到了一些重要的調試技巧。
你可以到這裏下載改進後的項目代碼。