1. 程式人生 > >iOS應用崩潰日誌揭祕

iOS應用崩潰日誌揭祕


作為一名應用開發者,你是否有過如下經歷?

Learn how to make sense of crash logs!
為確保你的應用正確無誤,在將其提交到應用商店之前,你必定進行了大量的測試工作。它在你的裝置上也執行得很好,但是,上了應用商店後,還是有使用者抱怨會閃退 !
如果你跟我一樣是個完美主義者,你肯定想將應用做到盡善盡美。於是你開啟程式碼準備修復閃退的問題……但是,從何處著手呢?
這時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同步,並根據作業系統的不同,到上述位置把崩潰日誌下載下來,然後通過電子郵件傳送給你。
你必需儘量獲取使用者裝置生成的所有崩潰日誌。因為崩潰日誌越多,就越容易診斷問題所在!
另外,如果你裝了Xcode,也能很容易通過Xcode從你的裝置上獲得崩潰日誌。將iOS裝置連線到電腦上,然後開啟Xcode。從選單欄上選擇 Window  

選單, 然後選擇 Organizer (快捷方式是 Shift-CMD-2).
在 Organizer 視窗上, 選中 Devices 標籤欄. 在左側的導航面板上,選中 Device Logs, 如下圖所示:
Devices tab in Xcode Organizer
看看上圖,左側有好幾個 Device Logs 選單項. LIBRARY 下面的Device Logs是你所有裝置(曾經連線到Xcode的)的日誌 。每個裝置下面的 Device Logs 是對應裝置的日誌。
應用提交到App Store後,你也能從 iTunes Connect 獲取到使用者的崩潰日誌. 登入到 iTunes Connect 上, 選擇Manage Your Applications, 點選相應的應用, 點選應用圖示下面的 View Details 按鈕, 然後點選右欄Links部分的  Crash Reports 。
Crash Reports in iTunes Connect
如果沒有崩潰日誌,試試點選Refresh 按鈕重新整理一下。如果你的應用還賣得不多,或者剛上架不久,iTunes Connect賬號上也可能還沒有任何崩潰日誌。
如果iTunes Connect上有崩潰日誌,你將看到如下圖:
Crash Reports in iTunes Connect
有時,儘管有使用者報告閃退,你仍然看不到崩潰報告。這時,最好讓使用者直接把崩潰報告發送給你。

什麼情況下會產生崩潰日誌?

兩種主要情況會產生崩潰日誌:

  1. 應用違反作業系統規則。
  2. 應用中有Bug。

違反iOS規則包括在啟動、恢復、掛起、退出時watchdog超時、使用者強制退出和低記憶體終止。讓我們詳細瞭解一下吧。
Watchdog 超時機制
從iOS 4.x開始,退出應用時,應用不會立即終止,而是退到後臺。
但是,如果你的應用響應不夠快,作業系統有可能會終止你的應用,併產生一個崩潰日誌。這些事件與下列UIApplicationDelegate方法相對應:

  • application:didFinishLaunchingWithOptions:
  • applicationWillResignActive:
  • applicationDidEnterBackground:
  • applicationWillEnterForeground:
  • applicationDidBecomeActive:
  • applicationWillTerminate:

上面所有這些方法,應用只有有限的時間去完成處理。如果花費時間太長,作業系統將終止應用。

注意: 如果你沒有把需要花費時間比較長的操作(如網路訪問)放在後臺執行緒上就很容易發生這種情況。關於如果避免這種情況的資訊,請參考我們的另外兩篇教程: Grand Central Dispatch 和 NSOperations

使用者強制退出
iOS 4.x開始支援多工。如果應用阻塞介面並停止響應, 使用者可以通過在主螢幕上雙擊Home按鈕來終止應用。此時,操作應用將生成一個崩潰日誌。

注意: 雙擊Home按鈕後,你將看到執行過的所有應用。那些應用不一定是正在執行,也不一定是被掛起。
通常,使用者點選Home按鈕時,應用將在後臺保留約10分鐘,然後作業系統自動將其終止。 所以雙擊Home按鈕顯示的應用列表只是表明那是一系列過去開啟過的應用。刪除那些應用的圖示不會產生任何崩潰日誌。

低記憶體終止
子類化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) 程序資訊
第一部分是閃退程序的相關資訊。

  • 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

它包括四列:

  1. 幀編號—— 此處是2。
  2. 二進位制庫的名稱 ——此處是 XYZLib.
  3. 呼叫方法的地址 ——此處是 0x34648e88.
  4. 第四列分為兩個子列,一個基本地址和一個偏移量。此處是0×83000 + 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
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 按鈕。
本教程後面將會學習如何研究低記憶體崩潰日誌。

異常編碼

在研究真實閃退場景之前,還有一點需要重點介紹一下:就是那些有趣的異常編碼 。
你可以在報告的異常部分——前面程式碼的第3部分找到異常編碼。有些編碼比較常見。
通常,異常編碼以一些文字開頭,緊接著是一個或多個十六進位制值,此數值正是說明閃退根本性質的所在。  從這些編碼中,可以區分出閃退是因為程式錯誤、非法記憶體訪問或者是其他原因。
下面是一些常見的異常編碼:

  • 0x8badf00d: 讀做 “ate bad food”! (把數字換成字母,是不是很像 :p)該編碼表示應用是因為發生watchdog超時而被iOS終止的。  通常是應用花費太多時間而無法啟動、終止或響應用系統事件。

  • 0xbad22222: 該編碼表示 VoIP 應用因為過於頻繁重啟而被終止。
  • 0xdead10cc: 讀做 “dead lock”!該程式碼表明應用因為在後臺執行時佔用系統資源,如通訊錄資料庫不釋放而被終止 。
  • 0xdeadfa11: 讀做 “dead fall”! 該程式碼表示應用是被使用者強制退出的。根據蘋果文件, 強制退出發生在使用者長按開關按鈕直到出現 “滑動來關機”, 然後長按 Home按鈕。強制退出將產生 包含0xdeadfa11 異常編碼的崩潰日誌, 因為大多數是強制退出是因為應用阻塞了介面。

注意: 在後臺任務列表中關閉已掛起的應用不會產生崩潰日誌。 一旦應用被掛起,它何時被終止都是合理的。所以不會產生崩潰日誌。

大展身手的時候到了!

好了! 你已經學習了所有分析崩潰日誌和修復錯誤的基礎知識!
假設你剛進入Rage-O-Rage有限公司工作。該公司有一個在App Store上熱銷的應用,叫 Rage Masters。
你的老闆安迪要你幫忙解決幾個使用者經常抱怨閃退問題。你的任務就是研究這些閃退,符號化使用者提供的崩潰日誌,查詢問題所在,並修復之。
你可以從 這裡下載應用的原始碼。

注意: 如果你想自己重新生成崩潰報告,請遵照以下指引:

  1. 下載原始碼然後在Xcode中開啟工程檔案。
  2. 使用正確的provisioning profile連線到iOS裝置。
  3. 從Xcode工具欄上選擇iOS裝置——不是模擬器作為target,然後構建應用。
  4. 當你在裝置上到預設頁面(應用的全屏圖片)時,立即在Xcode上點選停止按鈕。
  5. 關閉 Xcode。
  6. 在裝置上直接開啟應用。
  7. 測試場景,完成後連線裝置到電腦上,通過Xcode獲取崩潰日誌。

場景 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.

Network calls on the main thread makes kittens sad.

不管如何,問題得你來修復了。這個呼叫必需非同步進行,甚至更理想的情況是,在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];
         }];

場景 2: 無法響應事件的按鈕

一名使用者說: “我不能將某個rage master新增到書籤裡面。我想新增的時候應用就閃退…”
用一名使用者說 :”書籤不能用 … 在詳細頁面上,點選書籤按鈕,應用就閃退了!”
上面的抱怨說得不是很清楚,引起問題的原因肯定有多樣。看看崩潰日誌:

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檔案, 因為那是書籤按鈕實現動作的地方。 找到書籤功能程式碼:

-(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檔案) ,確認按鈕連線的正確性。

就是它了! 在 MainStoryboard.storyboard,按鈕連線的是 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

Processes
     Name                    &lt;UUID&gt;                       rpages       recent_max       [reason]          (state)
 
             lsd &lt;6a9f5b5f36b23fc78f87b6d8f1f49a9d&gt;          331              331         [vm]         (daemon) (idle)
            afcd &lt;b0aff2e7952e34a9882fec81a8dcdbb2&gt;          141              141         [vm]         (daemon) (idle)
    itunesstored &lt;4e0cd9f873de3435b4119c48b2d6d13d&gt;         1761             1761         [vm]         (daemon) (idle)
softwareupdatese &lt;2bc4b5ae016431c98d3b34f81027d0ae&gt;          311              311         [vm]         (daemon) (idle)
          Amazon &lt;4600481f07ec3e59a925319b7f67ba14&gt;         2951             2951         [vm]         (suspended)
       accountsd &lt;ac0fce15c1a2350d951efc498d521ac7&gt;          519              519         [vm]         (daemon) (idle)
coresymbolicatio &lt;edba67001f76313b992056c712153b4b&gt;          126              126         [vm]         (daemon) (idle)
           Skype &lt;504cf2fe60cb3cdea8273e74df09836b&gt;         3187             3187         [vm]         (background)
      MobileMail &lt;bff817c61ce33c85a43ea9a6c98c29f5&gt;        14927            14927         [vm]         (continuous)
       MobileSMS &lt;46778de076363d67aeea207464cfc581&gt;         2134             2134         [vm]         (background)
     MobilePhone &lt;3fca241f2a193d0fb8264218d296ea41&gt;         2689             2689         [vm]         (continuous)
      librariand &lt;c9a9be81aa9632f0a913ce79b911f27e&gt;          317              317         [vm]         (daemon)
             kbd &lt;3e7136ddcefc3d77a01499db593466cd&gt;          616              616         [vm]         (daemon)
            tccd &lt;eb5ddcf533663f8d987d67cae6a4c4ea&gt;          224              224         [vm]         (daemon)
    Rage Masters &lt;90b45d6281e934209c5b06cf7dc4d492&gt;        28591            28591         [vm]         (frontmost) (resume)
            ptpd &lt;04a56fce67053c57a7979aeea8e5a7ea&gt;          879              879                      (daemon)
   iaptransportd &lt;f784f30dc09d32078d87b450e8113ef6&gt;          230              230                      (daemon)
       locationd &lt;892cd1c9ffa43c99a82dba197be5f09e&gt;         1641             1641                      (daemon)
         syslogd &lt;cbef142fa0a839f0885afb693fb169c3&gt;          237              237                      (daemon)
    mediaserverd &lt;80657170daca32c9b8f3a6b1faac43a2&gt;         4869             4869                      (daemon)
     dataaccessd &lt;2a3f6a518f3f3646bf35eddd36f25005&gt;         1786             1786                      (daemon)
      aosnotifyd &lt;d4d14f2914c3343796e447cfef3e6542&gt;          549              549                      (daemon)
           wifid &lt;9472b090746237998cdbb9b34f090d0c&gt;          455              455                      (daemon)
     SpringBoard &lt;27372aae101f3bbc87804edc10314af3&gt;        18749            18749                     
      backboardd &lt;5037235f295b33eda98eb5c72c098858&gt;         5801             5801                      (daemon)
  UserEventAgent &lt;6edfd8d8dba23187b05772dcdfc94f90&gt;          601              601                      (daemon)
    mediaremoted &lt;4ff39c50c684302492e396ace813cb25&gt;          293              293                      (daemon)
     pasteboardd &lt;8a4279b78e4a321f84a076a711dc1c51&gt;          176              176                      (daemon)
springboardservi &lt;ff6f64b3a21a39c9a1793321eefa5304&gt;            0                0                      (daemon)
    syslog_relay &lt;45e9844605d737a08368b5215bb54426&gt;            0                0                      (daemon)
      DTMobileIS &lt;23303ca402aa3705870b01a9047854ea&gt;            0                0                      (daemon)
notification_pro &lt;845b7beebc8538ca9ceef731031983b7&gt;          169              169                      (daemon)
    syslog_relay &lt;45e9844605d737a08368b5215bb54426&gt;            0                0                      (daemon)
             ubd &lt;74dc476d1785300e9fcda555fcb8d774&gt;          976              976                      (daemon)
        twitterd &lt;4b4946378a9c397d8250965d17055b8e&gt;          730              730                      (daemon)
         configd &lt;4245d73a9e96360399452cf6b8671844&gt;          809              809                      (daemon)
   absinthed.N94 &lt;7f4164c844fa340caa940b863c901aa9&gt;           99               99                      (daemon)
filecoordination &lt;fbab576f37a63b56a1039153fc1aa7d8&gt;          226              226                      (daemon)
       distnoted &lt;a89af76ec8633ac2bbe99bc2b7964bb0&gt;          137              137                      (daemon)
            apsd &lt;94d8051dd5f5362f82d775bc279ae608&gt;          373              373                      (daemon)
        networkd &lt;0032f46009f53a6c80973fe153d1a588&gt;          219              219                      (daemon)
      aggregated &lt;8c3c991dc4153bc38aee1e841864d088&gt;          112              112                      (daemon)
        BTServer &lt;c92fbd7488e63be99ec9dbd05824f5e5&gt;          522              522                      (daemon)
   fairplayd.N94 &lt;7bd896bd00783a48906090d05cf1c86a&gt;          210              210                      (daemon)
       fseventsd &lt;996cc4ca03793184aea8d781b55bce08&gt;          384              384                      (daemon)
         imagent &lt;1e68080947be352590ce96b7a1d07b2f&gt;          586              586                      (daemon)
   mDNSResponder &lt;3e557693f3073697a58da6d27a827d97&gt;          295              295                      (daemon)
       lockdownd &lt;ba1358c7a8003f1b91af7d5f58dd5bbe&gt;          389              389                      (daemon)
          powerd &lt;2d2ffed5e69638aeba1b92ef124ed861&gt;          174              174                      (daemon)
      CommCenter &lt;1f425e1e897d32e8864fdd8eeaa803a8&gt;         2212             2212                      (daemon)
         notifyd &lt;51c0e03da8a93ac8a595442fcaac531f&gt;          211              211                      (daemon)
     ReportCrash &lt;8c32f231b2ed360bb151b2563bcaa363&gt;          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 程序是顯示主螢幕的應用,出現在你雙擊home按鈕等情況,而且它是一直活動的。
低記憶體發生時,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

當用戶點選執行按鈕, 應用開始一個背景執行緒,呼叫 lickLollipop 方法若干次,然後更新介面反映吃棒棒糖的數量。 lickLollipop 方法從屬性列表檔案(PLIST)檔案讀取一個長字串,然後新增到陣列上。這些資料並不重要, 能在不影響使用者體驗的前提下重新建立。
利用每種能夠清除和重建資料而不影響使用者體驗的情況是好習慣。這樣能夠方便地釋放記憶體,減少低記憶體警告。
那麼,如何提高程式碼質量呢? 實現 didReceiveMemoryWarning 方法,像下面這樣處理資料:

-(void)didReceiveMemoryWarning {
    [lollipops removeAllObjects];
    [super didReceiveMemoryWarning];
}

搞定!

下一步?

萬歲,你研究了4個閃退案例! 你的應用更完善了,並且學到了一些重要的除錯技巧。
你可以到這裡下載改進後的專案程式碼。
你喜歡iOS崩潰日誌揭祕嗎? 希望你能將學到的運用到你自己的應用中,也希望你能處理閃退,使你的應用更強壯!
如果你對本教程或崩潰日誌有問題或意見,可以在下面發表評論。