首页 > 代码库 > iOS应用崩溃日志分析 iOS应用崩溃日志揭秘
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
这篇文章还能够在这里找到 英语
本文作者是 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上有崩溃日志,你将看到例如以下图:
有时。虽然实用户报告闪退,你仍然看不到崩溃报告。这时,最好让用户直接把崩溃报告发送给你。
什么情况下会产生崩溃日志?
两种主要情况会产生崩溃日志:
- 应用违反操作系统规则。
- 应用中有Bug。
违反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: 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 |
它包含四列:
- 帧编号—— 此处是2。
- 二进制库的名称 ——此处是 XYZLib.
- 调用方法的地址 ——此处是 0x34648e88.
- 第四列分为两个子列,一个基本地址和一个偏移量。此处是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 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。
你的老板安迪要你帮忙解决几个用户常常抱怨闪退问题。你的任务就是研究这些闪退,符号化用户提供的崩溃日志,查找问题所在,并修复之。
你能够从 这里下载应用的源码。
注意: 假设你想自己又一次生成崩溃报告,请遵照下面指引:
- 下载源代码然后在Xcode中打开project文件。
- 使用正确的provisioning profile连接到iOS设备。
- 从Xcode工具栏上选择iOS设备——不是模拟器作为target,然后构建应用。
- 当你在设备上到默认页面(应用的全屏图片)时,马上在Xcode上点击停止button。
- 关闭 Xcode。
- 在设备上直接打开应用。
- 測试场景,完毕后连接设备到电脑上,通过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: ?? |
发现问题了吗?
异常编码是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]; }]; |
场景 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: ?? |
异常代码是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 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文件连接到正确的方法上。
两者方法都行。
又处理了一个闪退问题。好样的。:]
场景 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: ? |
这看起来跟前面那个崩溃日志非常像。是还有一个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 <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个闪退案例! 你的应用更完好了,而且学到了一些重要的调试技巧。
你能够到这里下载改进后的项目代码。
iOS应用崩溃日志分析 iOS应用崩溃日志揭秘