iOS开发-分析生产环境的Crash

前言

APP 的 Crash 一般会定位为严重问题,生产环境下的 APP 一般需要做到 Crash 率高于 99.8%。真实的生产环境可能十分复杂,可能存在极其恶劣的网络环境的影响,后台传输数据格式不准确,内存处理不当等,这些都可能引发 APP 的 Crash。开发环境下,很难做到完全容错,测试也几乎不可能覆盖所有场景。因此,行之有效的监测机制几乎是必然的。

Objective-C 的不安全性

在 Objective-C 中,有些数据结构或方法只能接收非空的值,如果我们在调用这些方法时,没有做好充分的判断,错误的传入一个空值,这时候编译阶段并不会给出警告,在程序的运行时才会直接崩溃。类似于这段代码:

NSData *data = [NSJSONSerialization dataWithJSONObject:array options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:data encoding:(NSUTF8StringEncoding)];

在上述代码中,当变量 array == nil 时,程序即会崩溃。这类问题在开发中一般是由于数据的改变,没有做足够的容错处理。如果测试的覆盖率不全,极有可能在线上发生崩溃。

在 Swift 中,在给上述方法传入 array 之前,需要对 array 做出明确的为空判断, 否则编译器会直接告警,一般会采用可选绑定(if...let)的形式对可选类型做判断,当能进入到对应的代码块,也就意味着 array 一定是有值的。

let array : [Any]? = ["Alex"]
if let array = array {
    let data = try? JSONSerialization.data(withJSONObject: array, options: JSONSerialization.WritingOptions.prettyPrinted)
}

Swift 可以通过自身的一些语言特性让这些问题避免在开发阶段,但是 Objective-C 很难做到。对于 Objective-C 开发的程序,非常有必要做一定的 Crash 监听工作。当监听到这些信息之后,可以通过版本迭代或 HotPatch 的形式及时作出修复,避免用户的流失。

在我们的项目中应用的是 友盟统计日志+dYSM分析工具 的形式,可以有效的解决一部分 Carsh。友盟统计只需要引入较少的代码,并在合适的时机调用极少的代码,无侵入性,容易移除。

友盟统计Crash日志

友盟统计具有分析流量来源、用户属性、行为数据、错误分析等特性,在我们的产品中一般会接入用来统计程序的Crash日志。

上线的 APP 集成了友盟统计后,当发生崩溃时,会在友盟统计后台的错误分析模块监听到崩溃日志。一般会看到两种错误类型:

1. 明确指出了报错API

*** +[NSJSONSerialization dataWithJSONObject:options:error:]: value parameter is nil
(null)
((
    0   CoreFoundation                      0x2268f933  + 150
    1   libobjc.A.dylib                     0x21e2ae17 objc_exception_throw + 38
    2   CoreFoundation                      0x2268f861  + 0
    3   Foundation                          0x22f28341  + 84
    4   fuzhuxian                           0x80099 fuzhuxian + 508057
    5   UIKit                               0x27060bd9  + 68
    6   UIKit                               0x27061283  + 30
    7   UIKit                               0x26f577e3  + 1230
    8   UIKit                               0x26f5aa85  + 192
    9   UIKit                               0x26d38157  + 90
    10  UIKit                               0x26c45ba5  + 540
    11  UIKit                               0x26c45685  + 204
    12  UIKit                               0x26c4557f  + 78
    13  QuartzCore                          0x24ca5689  + 252
    14  libdispatch.dylib                   0x221fd80f  + 22
    15  libdispatch.dylib                   0x2220bba9  + 1524
    16  CoreFoundation                      0x22651b6d  + 8
    17  CoreFoundation                      0x22650067  + 1574
    18  CoreFoundation                      0x2259f229 CFRunLoopRunSpecific + 520
    19  CoreFoundation                      0x2259f015 CFRunLoopRunInMode + 108
    20  GraphicsServices                    0x23b8fac9 GSEventRunModal + 160
    21  UIKit                               0x26c73189 UIApplicationMain + 144
    22  fuzhuxian                           0x29af9 fuzhuxian + 154361
    23  libdyld.dylib                       0x22247873  + 2
)

dSYM UUID: 679C384A-0751-3B66-8BAC-FB0DB78AC7B6
CPU Type: armv7
Slide Address: 0x00004000
Binary Image: fuzhuxian
Base Address: 0x0004b000

这一类的 Crash 明确的指出了崩溃的方法,上图中的问题是 NSJSONSerialization 对象的 dataWithJsonObject 方法传入了一个为空的参数,并且列出了报错的内存地址。类似的会打印出具体报错API的还有数组越界问题等。

2. Application received signal SIGSEGV

Application received signal SIGSEGV
(null)
((
    0   CoreFoundation                      0x21b47933  + 150
    1   libobjc.A.dylib                     0x212e2e17 objc_exception_throw + 38
    2   CoreFoundation                      0x21b47861  + 0
    3   fuzhuxian                           0x362d87 fuzhuxian + 3534215
    4   libsystem_platform.dylib            0x2187606f _sigtramp + 34
    5   Foundation                          0x22365af5 __NSFireDelayedPerform + 468
    6   CoreFoundation                      0x21b0a58f  + 14
    7   CoreFoundation                      0x21b0a1c1  + 936
    8   CoreFoundation                      0x21b0800d  + 1484
    9   CoreFoundation                      0x21a57229 CFRunLoopRunSpecific + 520
    10  CoreFoundation                      0x21a57015 CFRunLoopRunInMode + 108
    11  GraphicsServices                    0x23047ac9 GSEventRunModal + 160
    12  UIKit                               0x2612b189 UIApplicationMain + 144
    13  fuzhuxian                           0x29c41 fuzhuxian + 154689
    14  libdyld.dylib                       0x216ff873  + 2
)

dSYM UUID: 27C7888F-E1F2-33DE-96ED-6031F25C66EC
CPU Type: armv7
Slide Address: 0x00004000
Binary Image: fuzhuxian
Base Address: 0x000f0000

这一类问题没有指出具体报错的方法,但是给出了可用来分析的内存地址 0x362d870x29c41。出现这种问题的原因一般是程序访问了无效的内存。

实际上仅仅有这些报错的 API 或者 16 进制内存地址意义不大,因为依然不能反应出代码中的哪一个文件的哪一列出了错误。为了找出崩溃的具体位置,需要借助 dYSM 文件进行分析。

dYSM文件

dSYM 是保存 16 进制函数地址映射信息的中转文件,我们调试的 symbols 都会包含在这个文件中,并且每次编译项目的时候都会生成一个新的 dSYM 文件,位于 /Users/<用户名>/Library/Developer/Xcode/Archives 目录下。Xcode 编译项目后会看到一个同名的 dSYM 文件。

每一个 xx.app 和 xx.app.dSYM 文件都有对应的 UUID,crash 文件也有自己的 UUID,只要这三个文件的 UUID 一致,我们就可以通过他们解析出正确的错误函数信息了。因此,对于每一个发布版本我们都很有必要保存对应的 Archives 文件 。

dSYM分析工具

我们的项目中用到了 dSYMTools 作为分析工具分析 dSYM 文件,源码可以直接编译成 Mac APP。它的优点在于直接面向 .xcarchive 文件进行分析,不需要再去找对应的 dSYM 文件,系统会自动找到对应的 dSYM 文件。

iOS开发-分析生产环境的Crash_第1张图片
dSYMTools.png

可以直接将 achieve 出来的 .xcarchive 文件直接拖入到文件选择区域,选择对应的 CPU 类型(一般 iPhone5S 和 iPad Air 之后的是 arm64 类型),工具会默认匹配出可执行文件的 UDID 和 默认 Slide Address,只需要再键入报错的内存地址,如上文代错误内存代码中的 0x80099 0x29af9 0x362d87 0x29c41,点击分析,即可打印出可能出错的地方。

总结

对于每一个发布版本我们都很有必要保存对应的 Archives 文件 ,这个文件记录了
APP 的关键信息,可以作为日后分析问题的重要途径。

在生产环境下通过集成友盟或其他 SDK 的形式监测 Crash 报告,这实际是一种滞后的监听机制,它并不能实时的改变代码、修复线上 Bug,但是可以提升程序员的信心。对于开发者来说不失是一种有效的解决问题的方法。

Swift 可以在开发阶段有效的避免很大一部分上述问题 Xcode 自动的提示节省了一大部分思考的时间,让我们和你专注于业务逻辑的处理上,又保证了安全性,这对于开发者而言无非是一件好事。当然在写 Objective-C 时,基本的风险预估和容错处理也必不可少。

你可能感兴趣的:(iOS开发-分析生产环境的Crash)