iOS奔潰問題處理經(jīng)驗

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

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

常見的奔潰信息

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

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

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

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

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

  1. 自定義加強版的內(nèi)測奔潰收集
    內(nèi)部測試時,用Crashlytics當(dāng)然也是可以的。但第三方奔潰收集在和用戶交互方面是一個短板。當(dāng)你老板在用你的應(yīng)用突然奔潰時,他的怒不可遏是可以想象的。然后他耐心的打來電話要報告這個奔潰,你卻告訴他你只能看到一堆奔潰日志,看不到他在哪個界面,操作哪個按鈕,發(fā)送的哪個請求,輸入了什么文字,反正是什么都不知道,你覺得老板年底能放過你嗎?
    對于內(nèi)測用戶,稍許復(fù)雜的反饋機制是可以接受的,因為大家的目的都是為了改良產(chǎn)品。所以可以適當(dāng)增加一些反饋的信息,我們比較推薦的是在奔潰時,除常規(guī)的奔潰日志,可以增加log日志,屏幕抓圖這兩項內(nèi)容。
    A. log日志的保存及獲得:
    采用CocoaLumberjack這類第三方庫打印log是比較合適的方案,根據(jù)需要,CocoaLumberjack可以打印log到文件,在奔潰的時候,取log文件直接發(fā)送即可:
    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,增加代碼發(fā)送log文件到奔潰平臺
        }
    }

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

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.奔潰現(xiàn)場抓?。罕紳⑷罩究梢圆捎肗SException類,設(shè)置奔潰處理函數(shù):

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

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

一個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

郵件發(fā)送的代碼:

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


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

產(chǎn)生一個新的crash日志文件,就已經(jīng)是完成符號轉(zhuǎn)換后的了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容