什么是语言异常呢?简单来说就是由于不规范编写代码造成的问题称之为语言异常。
比如数组越界、调用某个类未实现的方法等等。那么如何通过看崩溃报告来确定崩溃是否由语言异常导致的呢?首先要看的就是 Exception information 这一栏是否遵循下面的范式:
Exception Type: EXC_CRASH (SIGABRT)
Exception Codes: 0x0000000000000000, 0x0000000000000000
Triggered by Thread: 0
还有就是,当崩溃是由于未捕获的语言异常导致的时候,崩溃报告中肯定会包含 Last Exception Backtrace
这一栏信息:
下面,我们通过详细分析Last Exception Backtrace
这一栏信息,来去定位是什么原因导致的崩溃。
Last Exception Backtrace 分析
在这一栏,操作系统通常会记录关于当前崩溃的完整函数调用栈。该栏的回溯会以明确抛出语言异常的帧结尾。
在函数回溯的过程中,你会发现有关抛出异常的方法的关键信息,以及代码的哪个部分调用了引发异常的方法。比如下面的例子:
崩溃代码:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSArray *arr = @[];
NSLog(@"%d", arr[5]);
}
Last Exception Backtrace:
0 CoreFoundation 0x191560e38 __exceptionPreprocess + 164
1 libobjc.A.dylib 0x18a6f78d8 objc_exception_throw + 60
2 CoreFoundation 0x191672b48 __CFArrayHash + 0
3 CrashDemo 0x1027d5e18 -[ViewController touchesBegan:withEvent:] + 116
4 UIKitCore 0x19392ddfc forwardTouchMethod + 284
5 UIKitCore 0x1938281b0 -[UIWindow _sendTouchesForEvent:] + 356
6 UIKitCore 0x193827770 -[UIWindow sendEvent:] + 3284
7 UIKitCore 0x193826a20 -[UIApplication sendEvent:] + 676
8 UIKitCore 0x1938260d8 __dispatchPreprocessedEventFromEventQueue + 7084
9 UIKitCore 0x19386de00 __processEventQueue + 5632
10 UIKitCore 0x1944cb820 updateCycleEntry + 168
11 UIKitCore 0x193d7e5b0 _UIUpdateSequenceRun + 84
12 UIKitCore 0x1943cd310 schedulerStepScheduledMainSection + 172
13 UIKitCore 0x1943cc4dc runloopSourceCallback + 92
14 CoreFoundation 0x19162cf24 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 28
15 CoreFoundation 0x1916392fc __CFRunLoopDoSource0 + 176
16 CoreFoundation 0x1915bd1c0 __CFRunLoopDoSources0 + 244
17 CoreFoundation 0x1915d2b7c __CFRunLoopRun + 836
18 CoreFoundation 0x1915d7eb0 CFRunLoopRunSpecific + 612
19 GraphicsServices 0x1cb7cd368 GSEventRunModal + 164
20 UIKitCore 0x193acd668 -[UIApplication _run] + 888
21 UIKitCore 0x193acd2cc UIApplicationMain + 340
22 CrashDemo 0x1027d60b8 main + 120
23 dyld 0x1afed0960 start + 2528
报告解读:
首先,第 0 – 1 帧可以看出是操作系统抛出了异常。第 2 帧可以分析出当前异常是与 NSArray 有关的。第 3 帧可以看出是 ViewController
的 touchesBegan:withEvent:
调用导致了该崩溃的发生。定位到这,我们就可以去相应的代码那,去具体分析到底是数组的什么操作导致了该崩溃的发生。
第 4 – 23 帧则是 app 启动和事件转发的过程,跟崩溃原因调查关系不大。
Tips:如果 API 抛出 doesNotRecognizeSelector(_:)
的异常,那么这个异常可能是由于僵尸对象造成的。如何分析可以看这里。
查看异常信息
操作系统提供的未捕获异常处理程序在终止进程之前,会将异常消息记录到控制台。所以,如果你能复现,你会在控制台看到如下的打印:
***** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArray0 objectAtIndex:]: index 5 beyond bounds for empty NSArray'**
这个信息就可以很明显的看出是由于数组越界而导致的崩溃了。
那为什么崩溃报告不包含这么简单明了的日志呢?下面是 Apple 的回答:
iOS、iPadOS、watchOS 和 tvOS 崩溃报告不包含异常消息,以防止通过异常消息泄露有关用户的私人信息。
分析系统语言异常引起的崩溃
在确定是操作系统的 API 抛出异常后,我们应该查阅该 API 的文档来确定触发异常的条件。还尝试使用 Xcode 调试器去重现崩溃,以在控制台中获取有关异常的其他信息。最后,可以使用回溯中的帧作为需要测试的特定代码的方向。
如果无法重现崩溃的话,就需要使用所有的线程回溯(不仅仅是异常回溯)作为线索,去分析当崩溃发生时,程序到底在做什么。