前言
APP 的 Crash 一般會定位為嚴重問題聂渊,生產(chǎn)環(huán)境下的 APP 一般需要做到 Crash 率高于 99.8%。真實的生產(chǎn)環(huán)境可能十分復(fù)雜劝堪,可能存在極其惡劣的網(wǎng)絡(luò)環(huán)境的影響墓怀,后臺傳輸數(shù)據(jù)格式不準確,內(nèi)存處理不當?shù)忍浚@些都可能引發(fā) APP 的 Crash炼七。開發(fā)環(huán)境下,很難做到完全容錯布持,測試也幾乎不可能覆蓋所有場景豌拙。因此,行之有效的監(jiān)測機制幾乎是必然的题暖。
Objective-C 的不安全性
在 Objective-C 中姆蘸,有些數(shù)據(jù)結(jié)構(gòu)或方法只能接收非空的值,如果我們在調(diào)用這些方法時芙委,沒有做好充分的判斷交汤,錯誤的傳入一個空值,這時候編譯階段并不會給出警告细溅,在程序的運行時才會直接崩潰卖陵。類似于這段代碼:
NSData *data = [NSJSONSerialization dataWithJSONObject:array options:NSJSONWritingPrettyPrinted error:nil];
NSString *jsonString = [[NSString alloc] initWithData:data encoding:(NSUTF8StringEncoding)];
在上述代碼中,當變量 array == nil
時侧啼,程序即會崩潰牛柒。這類問題在開發(fā)中一般是由于數(shù)據(jù)的改變,沒有做足夠的容錯處理痊乾。如果測試的覆蓋率不全皮壁,極有可能在線上發(fā)生崩潰。
在 Swift 中哪审,在給上述方法傳入 array 之前蛾魄,需要對 array 做出明確的為空判斷, 否則編譯器會直接告警湿滓,一般會采用可選綁定(if...let
)的形式對可選類型做判斷滴须,當能進入到對應(yīng)的代碼塊,也就意味著 array 一定是有值的叽奥。
let array : [Any]? = ["Alex"]
if let array = array {
let data = try? JSONSerialization.data(withJSONObject: array, options: JSONSerialization.WritingOptions.prettyPrinted)
}
Swift 可以通過自身的一些語言特性讓這些問題避免在開發(fā)階段扔水,但是 Objective-C 很難做到。對于 Objective-C 開發(fā)的程序朝氓,非常有必要做一定的 Crash 監(jiān)聽工作魔市。當監(jiān)聽到這些信息之后主届,可以通過版本迭代或 HotPatch
的形式及時作出修復(fù),避免用戶的流失待德。
在我們的項目中應(yīng)用的是 友盟統(tǒng)計日志+dYSM分析工具 的形式岂膳,可以有效的解決一部分 Carsh。友盟統(tǒng)計只需要引入較少的代碼磅网,并在合適的時機調(diào)用極少的代碼谈截,無侵入性,容易移除涧偷。
友盟統(tǒng)計Crash日志
友盟統(tǒng)計具有分析流量來源簸喂、用戶屬性、行為數(shù)據(jù)燎潮、錯誤分析等特性喻鳄,在我們的產(chǎn)品中一般會接入用來統(tǒng)計程序的Crash日志。
上線的 APP 集成了友盟統(tǒng)計后确封,當發(fā)生崩潰時除呵,會在友盟統(tǒng)計后臺的錯誤分析模塊監(jiān)聽到崩潰日志。一般會看到兩種錯誤類型:
1. 明確指出了報錯API
*** +[NSJSONSerialization dataWithJSONObject:options:error:]: value parameter is nil
(null)
((
0 CoreFoundation 0x2268f933 <redacted> + 150
1 libobjc.A.dylib 0x21e2ae17 objc_exception_throw + 38
2 CoreFoundation 0x2268f861 <redacted> + 0
3 Foundation 0x22f28341 <redacted> + 84
4 fuzhuxian 0x80099 fuzhuxian + 508057
5 UIKit 0x27060bd9 <redacted> + 68
6 UIKit 0x27061283 <redacted> + 30
7 UIKit 0x26f577e3 <redacted> + 1230
8 UIKit 0x26f5aa85 <redacted> + 192
9 UIKit 0x26d38157 <redacted> + 90
10 UIKit 0x26c45ba5 <redacted> + 540
11 UIKit 0x26c45685 <redacted> + 204
12 UIKit 0x26c4557f <redacted> + 78
13 QuartzCore 0x24ca5689 <redacted> + 252
14 libdispatch.dylib 0x221fd80f <redacted> + 22
15 libdispatch.dylib 0x2220bba9 <redacted> + 1524
16 CoreFoundation 0x22651b6d <redacted> + 8
17 CoreFoundation 0x22650067 <redacted> + 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 <redacted> + 2
)
dSYM UUID: 679C384A-0751-3B66-8BAC-FB0DB78AC7B6
CPU Type: armv7
Slide Address: 0x00004000
Binary Image: fuzhuxian
Base Address: 0x0004b000
這一類的 Crash 明確的指出了崩潰的方法爪喘,上圖中的問題是 NSJSONSerialization
對象的 dataWithJsonObject
方法傳入了一個為空的參數(shù)颜曾,并且列出了報錯的內(nèi)存地址。類似的會打印出具體報錯API的還有數(shù)組越界問題等秉剑。
2. Application received signal SIGSEGV
Application received signal SIGSEGV
(null)
((
0 CoreFoundation 0x21b47933 <redacted> + 150
1 libobjc.A.dylib 0x212e2e17 objc_exception_throw + 38
2 CoreFoundation 0x21b47861 <redacted> + 0
3 fuzhuxian 0x362d87 fuzhuxian + 3534215
4 libsystem_platform.dylib 0x2187606f _sigtramp + 34
5 Foundation 0x22365af5 __NSFireDelayedPerform + 468
6 CoreFoundation 0x21b0a58f <redacted> + 14
7 CoreFoundation 0x21b0a1c1 <redacted> + 936
8 CoreFoundation 0x21b0800d <redacted> + 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 <redacted> + 2
)
dSYM UUID: 27C7888F-E1F2-33DE-96ED-6031F25C66EC
CPU Type: armv7
Slide Address: 0x00004000
Binary Image: fuzhuxian
Base Address: 0x000f0000
這一類問題沒有指出具體報錯的方法泛豪,但是給出了可用來分析的內(nèi)存地址 0x362d87
和 0x29c41
。出現(xiàn)這種問題的原因一般是程序訪問了無效的內(nèi)存侦鹏。
實際上僅僅有這些報錯的 API 或者 16 進制內(nèi)存地址意義不大诡曙,因為依然不能反應(yīng)出代碼中的哪一個文件的哪一列出了錯誤。為了找出崩潰的具體位置略水,需要借助 dYSM 文件進行分析价卤。
dYSM文件
dSYM 是保存 16 進制函數(shù)地址映射信息的中轉(zhuǎn)文件,我們調(diào)試的 symbols 都會包含在這個文件中渊涝,并且每次編譯項目的時候都會生成一個新的 dSYM 文件慎璧,位于 /Users/<用戶名>/Library/Developer/Xcode/Archives
目錄下。Xcode 編譯項目后會看到一個同名的 dSYM 文件驶赏。
每一個 xx.app 和 xx.app.dSYM 文件都有對應(yīng)的 UUID
炸卑,crash 文件也有自己的 UUID
既鞠,只要這三個文件的 UUID
一致煤傍,我們就可以通過他們解析出正確的錯誤函數(shù)信息了。因此嘱蛋,對于每一個發(fā)布版本我們都很有必要保存對應(yīng)的 Archives
文件 蚯姆。
dSYM分析工具
我們的項目中用到了 dSYMTools 作為分析工具分析 dSYM 文件五续,源碼可以直接編譯成 Mac APP。它的優(yōu)點在于直接面向 .xcarchive
文件進行分析龄恋,不需要再去找對應(yīng)的 dSYM 文件疙驾,系統(tǒng)會自動找到對應(yīng)的 dSYM 文件。
可以直接將 achieve
出來的 .xcarchive
文件直接拖入到文件選擇區(qū)域郭毕,選擇對應(yīng)的 CPU 類型(一般 iPhone5S 和 iPad Air 之后的是 arm64
類型)它碎,工具會默認匹配出可執(zhí)行文件的 UDID
和 默認 Slide Address
,只需要再鍵入報錯的內(nèi)存地址显押,如上文代錯誤內(nèi)存代碼中的 0x80099
0x29af9
0x362d87
0x29c41
扳肛,點擊分析,即可打印出可能出錯的地方乘碑。
總結(jié)
對于每一個發(fā)布版本我們都很有必要保存對應(yīng)的 Archives
文件 挖息,這個文件記錄了
APP 的關(guān)鍵信息,可以作為日后分析問題的重要途徑兽肤。
在生產(chǎn)環(huán)境下通過集成友盟或其他 SDK 的形式監(jiān)測 Crash 報告套腹,這實際是一種滯后的監(jiān)聽機制,它并不能實時的改變代碼资铡、修復(fù)線上 Bug电禀,但是可以提升程序員的信心。對于開發(fā)者來說不失是一種有效的解決問題的方法笤休。
Swift 可以在開發(fā)階段有效的避免很大一部分上述問題 Xcode 自動的提示節(jié)省了一大部分思考的時間鞭呕,讓我們和你專注于業(yè)務(wù)邏輯的處理上,又保證了安全性宛官,這對于開發(fā)者而言無非是一件好事葫松。當然在寫 Objective-C 時,基本的風(fēng)險預(yù)估和容錯處理也必不可少底洗。