iOS奔潰問題處理經驗

面對形形色色的奔潰問題,作為一個老碼農,從最初的不知所措,慢慢也學會了和其共存共生。畢竟奔潰抓不完,但如何更好地抓奔潰卻是個永恒的話題。從iOS發展的這數年來,關于奔潰的處理早有成熟與完整的解決方案,而此次實踐,莫如說是給這個方案再增添一些小小的裝飾罷了。

  1. 收集奔潰
    收集崩潰大致有以下幾種方式:
    A. 蘋果自帶奔潰收集系統。通過iTunes Connect(Manage Your Applications - View Details - Crash Reports)打開奔潰控制開關,用戶同意隱私控制后即可收集奔潰。由于需要用戶主動認可,此方式能收集的奔潰并不太多。
    B. 第三方奔潰收集平臺。本人常用Fabric的Crashlytics,這個平臺的優點在于,除收集奔潰信息外,能多維度產生日活,奔潰數據的日,周,月等圖線,有助于開發乃至產品分析。
    C.自己開發的奔潰收集平臺。在NSException類提供的NSSetUncaughtExceptionHandler函數設置奔潰截獲代碼,即可在奔潰發生時執行自定義的奔潰處理,常見的奔潰處理信息可以包含奔潰現場的call stack,界面信息,用戶信息,業務信息等,可視各產品的需要來自己定制。
  2. 奔潰分析
    以下是Crashlytics中一段常見的奔潰日志:

常見的奔潰信息

奔潰信息包括發生時間,奔潰類型,最后停留的代碼位置及奔潰原因,以及奔潰代碼的call stack信息。
有一般經驗的開發人員,對上面的奔潰處理應該會比較得心應手。這就是一個函數名無效的錯誤,原因是數據類型不是期待的NSNumber型而變成了NSNull,這類錯誤的處理應該是比較簡單的。
那下面這個呢?

完全不知道怎么回事,有沒有?
僅有的線索:1. iOS7專享crash 2. 某一個UITextField輸入框的自動布局沒有觸發 。怎么查。如同大海撈針。
有沒有更進一步的線索呢?其實可以有的。
當我們做應用埋點統計的時候,常常想埋得越全越好,因為產品總會不停得增加埋點,最后還不如一次性全覆蓋到。那奔潰日志是不是也可以參考這種模式?打印出奔潰當時的ViewController名字怎么樣?
方式也非常簡單。ViewController的名字,可以直接通過取它的類名。獲取的時機,比較適合的是viewWillAppear,并且也可以用swizzling的方式全局獲得。當然,如果頁面共用很多,繼承關系復雜的情況下,還是建議到每個頁面自己去獲取吧。比如:

- (void) viewWillAppear:(BOOL)animated {
  [super viewWillAppear:animated];
//設置主線程名字,crash時記錄此name,可提高crash發現的幾率
  NSString*className = NSStringFromClass([self class]);
  if(className){
    [[NSThread mainThread] setName:className];
  }
}

非常簡單的代碼,就把主線程的名字替換成了當前ViewContronller的名字。再上線抓奔潰,結果就是這樣:

是不是大大縮小了范圍。一個小小的技巧能給查奔潰帶來多大的效益呢。

  1. 自定義加強版的內測奔潰收集
    內部測試時,用Crashlytics當然也是可以的。但第三方奔潰收集在和用戶交互方面是一個短板。當你老板在用你的應用突然奔潰時,他的怒不可遏是可以想象的。然后他耐心的打來電話要報告這個奔潰,你卻告訴他你只能看到一堆奔潰日志,看不到他在哪個界面,操作哪個按鈕,發送的哪個請求,輸入了什么文字,反正是什么都不知道,你覺得老板年底能放過你嗎?
    對于內測用戶,稍許復雜的反饋機制是可以接受的,因為大家的目的都是為了改良產品。所以可以適當增加一些反饋的信息,我們比較推薦的是在奔潰時,除常規的奔潰日志,可以增加log日志,屏幕抓圖這兩項內容。
    A. log日志的保存及獲得:
    采用CocoaLumberjack這類第三方庫打印log是比較合適的方案,根據需要,CocoaLumberjack可以打印log到文件,在奔潰的時候,取log文件直接發送即可:
    NSArray *loggers = [DDLog allLoggers];
    for (id logger in loggers){
        if ([logger isKindOfClass:[DDFileLogger class]]){
            NSString *logPath = ((DDFileLogger *)logger).logFileManager.logsDirectory;
            NSData *logData = [NSData dataWithContentsOfFile:logPath];
//ToDo,增加代碼發送log文件到奔潰平臺
        }
    }

B.屏幕抓圖是還原奔潰現場的一個有效的信息,一般奔潰平臺限于圖片文件過大,以及泄漏隱私的問題,很少提供屏幕抓圖功能。內測環境建議自行加上奔潰時的抓圖,方便開發定位界面:

UIGraphicsBeginImageContext([UIScreen mainScreen].bounds.size);
UIGraphicsBeginImageContextWithOptions([UIScreen mainScreen].bounds.size, NO, 0.0);
[self.window.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *libraryPath = [paths objectAtIndex:0];
NSString *path = [libraryPath stringByAppendingPathComponent:@"crashSnap.jpg"];
[UIImageJPEGRepresentation(image, 1.0) writeToFile:path atomically:YES];

C.奔潰現場抓取:奔潰日志可以采用NSException類,設置奔潰處理函數:

NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler);
void uncaughtExceptionHandler(NSException *exception) {
     NSLog(@"%@", [NSString stringWithFormat:@"MainThread Name: %@\n%@ \n %@", [NSThread mainThread].name, exception, exception.callStackSymbols]);
}

D.發送到收集奔潰渠道
收集奔潰的渠道很多,除去那些商用的以及免費的不說,常見的可以由應用服務器開一個接口來接收奔潰數據。這里介紹一種更適合iOS開發者以及個人的低成本的接受渠道,就是傳統的郵件。
通過郵件收集奔潰有不少好處,首先你不要集成那些龐大的sdk,也不用給后端提需求,只要自己默默地注冊一個郵箱。而且郵件能傳送的數據也比一般的后臺接口廣泛,文本,圖片,二進制文件都可以。展示上也可以根據需要自由選擇頁面或者客戶端。
發送郵件通常采用SMTP協議,遺憾的是現在許多免費郵箱都加強了SMTP的驗證碼機制,因此網易,騰訊,新浪等主流郵箱已經不能用,谷歌等被墻的更不必說,搜狐的似乎還是可以。
發送郵件我們參考了SKPSMTPMessage這個項目,并改寫了一些不能使用的方法。整個流程并不復雜,根據SMTP協議的要求,發起握手,傳輸標題、地址等,繼續傳輸正文,附件,然后結束。

一個SMTP傳輸示例:

S: 220 www.example.com ESMTP Postfix
C: HELO mydomain.com
S: 250 Hello mydomain.com
C: MAIL FROM: <sender@mydomain.com>
S: 250 Ok
C: RCPT TO: <friend@example.com>
S: 250 Ok
C: DATA
S: 354 End data with <CR><LF>.<CR><LF>
C: Subject: test message
C: From:""< sender@mydomain.com>
C: To:""< friend@example.com>
C:
C: Hello,
C: This is a test.
C: Goodbye.
C: .
S: 250 Ok: queued as 12345
C: quit
S: 221 Bye

郵件發送的代碼:

#import "MailSender.h"


@interface PBCrashReporter () <MailSenderDelegate>
@end

@implementation PBCrashReporter
- (void)sendFeedbackEmail
{
    MailSender *mailSender = [[MailSender alloc] init];
    mailSender.fromEmail = @"xxx@sohu.com";
    mailSender.toEmail = @"xxx@sohu.com";
    mailSender.relayHost = @"smtp.sohu.com";
    mailSender.requiresAuth = YES;
    mailSender.login = @"xxx@sohu.com";
    mailSender.pass = @"xxxxxx";
    mailSender.wantsSecure = NO;
    
    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
    NSString *userId =  [defaults stringForKey:kUserId];
    if (userId){
        mailSender.fromName = userId;
    }
    
    mailSender.subject = @"奔潰收集郵件";
    mailSender.delegate = self;
    
    
    NSDictionary *plainPart = [NSDictionary dictionaryWithObjectsAndKeys:@"text/plain; charset=UTF-8",smtpPartContentTypeKey,
                               @"crash日志,詳情見附件",smtpPartMessageKey,@"8bit",smtpPartContentTransferEncodingKey,nil];
    NSString *vcf1Path = [PBCrashReporter pathOfReportFile];
    NSData *vcf1Data = [NSData dataWithContentsOfFile:vcf1Path];
    

    NSDictionary *vcf1Part = [NSDictionary dictionaryWithObjectsAndKeys:@"text/directory;\r\n\tx-unix-mode=0644;\r\n\tname=\"crash.txt\"",smtpPartContentTypeKey,
                             @"attachment;\r\n\tfilename=\"crash.txt\"",smtpPartContentDispositionKey,[vcf1Data base64EncodedStringWithOptions:0],smtpPartMessageKey,@"base64",smtpPartContentTransferEncodingKey,nil];

    
    mailSender.parts = [NSArray arrayWithObjects:plainPart,vcf1Part,vcf2Part,nil];
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [mailSender sendMail];
    });
}

- (void)mailSent:(JFMailSender *)message
{
    //if something must run in main thread,please use dispatch_get_main_queue();
    NSLog(@"Yay! Message was sent!");
    [[NSFileManager defaultManager] removeItemAtPath:[PBCrashReporter pathOfReportFile] error:nil];
    [[NSFileManager defaultManager] removeItemAtPath:[PBCrashReporter pathOfSnapFile] error:nil];
}

- (void)mailFailed:(JFMailSender *)message error:(NSError *)error
{
    //if something must run in main thread,please use dispatch_get_main_queue();
    NSLog(@"%@", [NSString stringWithFormat:@"Darn! Error!\n%li: %@\n%@", (long)[error code], [error localizedDescription], [error localizedRecoverySuggestion]]);
}
@end

crash符號表解析
通過上面方法,自己收集到的奔潰日志,都是沒有經過解析的地址堆棧。需要轉換為函數名的堆棧信息,才能方便地找出問題所在。最方便使用的符號表解析工具是Xcode自帶的symbolicatecrash。
這個工具的使用方法已經有很多教程,這里我們給出一個最容易記憶的方法,就是兩個素材,一個工具,一條命令。
素材1:奔潰日志文件,可以是我們自己生成的crash日志文件
素材2: dSYM文件,打包時產生的符號地址映射文件
工具:symbolicatecrash
命令:


/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash  ./*.crash ./*.app.dSYM > symbol.crash

產生一個新的crash日志文件,就已經是完成符號轉換后的了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,781評論 18 139
  • 前言 iOS崩潰是讓iOS開發人員比較頭痛的事情,app崩潰了,說明代碼寫的有問題,這時如何快速定位到崩潰的地方很...
    齊滇大圣閱讀 65,400評論 29 443
  • 前言 崩潰是讓發人員比較頭痛的事情,app崩潰了,說明代碼寫的有問題,這時如何快速定位到崩潰的地方很重要。調試階段...
    進無盡閱讀 2,045評論 0 9
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,317評論 11 349
  • 1、第八章 Samba服務器2、第八章 NFS服務器3、第十章 Linux下DNS服務器配站點,域名解析概念命令:...
    哈熝少主閱讀 3,750評論 0 10