扒蟲篇-崩潰日志解讀及Crash收集

Paste_Image.png

前言

崩潰是讓發(fā)人員比較頭痛的事情,app崩潰了,說明代碼寫的有問題,這時如何快速定位到崩潰的地方很重要。調(diào)試階段是比較容易找到出問題的地方的,但是已經(jīng)上線的app并分析崩潰報告就比較麻煩了。最終,我們可以通過iOS崩潰日志在大多數(shù)情況下,你能從中了解到關(guān)于閃退的詳盡、有用的信息。線上崩潰可以通過 iTunesConnect 中心的Cash收集,也可以通過第三方Cash收集工具,亦或自己在工程中手動收集崩潰日志上傳到服務(wù)器中,本文做個小結(jié),希望對初入者能有些幫助。

崩潰

崩潰是由于程序拋出異常,系統(tǒng)異常結(jié)束的一種現(xiàn)象。我們可以先了解一下異常 NSException,這對于我們理解崩潰有幫助。NSException掌控著程序的生命,程序的崩潰就是NSException來控制的。其實主要的出發(fā)點(diǎn)是讓開發(fā)者認(rèn)識到哪里的代碼有問題。

** NSException**

這個樣子

其實控制臺輸出的日志信息就是NSException產(chǎn)生的,一旦程序拋出異常,程序就會崩潰,控制臺就會有這些崩潰日志。

下面代碼就會讓你的程序崩潰(下面代碼出自別人的文章,文末有原文出處)

//異常的名稱   
NSString *exceptionName = @"自定義異常";   
//異常的原因    
NSString *exceptionReason = @"我長得太帥了,所以程序崩潰了";    
//異常的信息   
NSDictionary *exceptionUserInfo = nil; 
NSException *exception = [NSException exceptionWithName:exceptionName reason:exceptionReason userInfo:exceptionUserInfo];       
NSString *aboutMe = @"太帥了";      
if ([aboutMe isEqualToString:@"太帥了"])     
{   //拋異常        
  @throw exception;    
}

崩潰截圖如下

1478827158887712.png

NSException的實用技巧

  • 1、 若自己封裝一套SDK,若要提示哪里出錯,那么就可以使用NSException。就像上面NSException的基本用法中的代碼一樣。

  • 2、可以用來捕獲異常,防止程序的崩潰。當(dāng)你意識到某段代碼可能存在崩潰的危險,那么你就可以通過捕獲異常來防止程序的崩潰。代碼如下

    @try {        
      //如果@try中的代碼會導(dǎo)致程序崩潰,就會來到@catch
      //將一個nil插入到可變數(shù)組中,這行代碼肯定有問題
      [arrayM addObject:nilStr];
     }    
    @catch (NSException *exception) {        
      //如果@try中的代碼有問題(導(dǎo)致崩潰),就會來到@catch
      //在這里你可以進(jìn)行相應(yīng)的處理操作
      //如果你要拋出異常(讓程序崩潰),就寫上 @throw exception
    }    
    @finally {        
      //@finally中的代碼是一定會執(zhí)行的
      //你可以在這里進(jìn)行一些相應(yīng)的操作
    }
    

崩潰日志

關(guān)于修復(fù)崩潰的Bug,如果你憑借自己的經(jīng)驗,有時候可能會遇到問題卡住,我想最快的方式就是通過分析崩潰日志來解決崩潰。

什么是崩潰日志,從哪里能得它
iOS設(shè)備上的應(yīng)用閃退時,操作系統(tǒng)會生成一個崩潰報告,也叫崩潰日志,保存在設(shè)備上。

崩潰日志上有很多有用的信息,包括應(yīng)用是什么情況下閃退的。通常,上面有每個正在執(zhí)行線程的完整堆棧跟蹤信息,所以你能從中了解到閃退發(fā)生時各線程都在做什么,并分辨出閃退發(fā)生在哪個線程上。

有幾種方法可以從設(shè)備上獲取崩潰日志。

  • xcode中查看崩潰信息
    xcode->Window->Organizer->Crashes


  • 通過Xcode查看設(shè)備崩潰信息
    除了上面的系統(tǒng)分析工具來進(jìn)行分析,如果是我們自己直接使用手機(jī)連接崩潰或者崩潰之后連接手機(jī),選擇window-> devices -> 選擇自己的手機(jī) -> view device logs 就可以查看我們的崩潰信息了。


  • 使用第三方軟件:itools
    如果你平時不用iTunes,而是使用itools這類第三方的軟件對iPhone設(shè)備進(jìn)行管理,也是沒問題的。


    打開itools,在你的設(shè)備下,找到“高級功能”,點(diǎn)擊“崩潰日志”,然后將需要的日志導(dǎo)出到電腦里面就可以了!

  • 應(yīng)用提交到App Store后,你也能從 iTunes Connect 獲取到用戶的崩潰日志. 登錄到 iTunes Connect 上, 選擇 Manage Your Applications, 點(diǎn)擊相應(yīng)的應(yīng)用, 點(diǎn)擊應(yīng)用圖標(biāo)下面的 View Details 按鈕, 然后點(diǎn)擊右欄Links部分的 Crash Reports 。

什么時候不會產(chǎn)生崩潰日志
以下情況不會有崩潰信息產(chǎn)生:

  • 內(nèi)存訪問錯誤(不是野指針錯誤)
  • 低內(nèi)存,當(dāng)程序內(nèi)存使用過多會造成系統(tǒng)低內(nèi)存的問題,系統(tǒng)會將程序內(nèi)存回收
  • 因為某種原因觸發(fā)看門狗機(jī)制

一般Xcode不輸出Crash日志有一下幾個可能:

  1. NSSetUncaughtExceptionHandler() 可能被重寫了,(比如你引用了一些第三方庫, 它的SDK里面可能包含了把Crash的日志上傳到服務(wù)器, 這樣這個日志可能被重寫了, 就不打印本地的崩潰信息了) 盡量把它放在didFinishLaunchingWithOptions 最后面的一行代碼塊里.

  2. 還一種崩潰的情況是 EXC_BAD_ACCESS ,EXC_BAD_ACCESS異常的本意是指訪問不到內(nèi)存中這個地址的值,可能是由于些變量已經(jīng)被回收了,亦可能是由于使用棧內(nèi)存的基本類型的數(shù)據(jù)賦值給了id類型的變量。當(dāng)遇到這種錯誤, 控制一般不會給你很多關(guān)于崩潰的信息, 這種崩潰你開啟僵尸對象模式即可, 不過記住你在正式發(fā)布的時候記得把這個勾取消, 不然會造成內(nèi)存泄漏。*

解析崩潰日志

.dSYM 文件

.dSYM 文件稱為符號表,是指在Xcode項目編譯后,在編譯生成的二進(jìn)制文件.app的同級目錄下生成的同名的.dSYM文件。

.dSYM文件其實是一個目錄,在子目錄中包含了一個16進(jìn)制的保存函數(shù)地址映射信息的中轉(zhuǎn)文件,所有Debug的symbols都在這個文件中(包括文件名、函數(shù)名、行號等),所以也稱之為調(diào)試符號信息文件。符號表就是用來符號化 crash log(崩潰日志)。crash log中有一些方法16進(jìn)制的內(nèi)存地址等,通過符號表就能找到對應(yīng)的能夠直觀看到的方法名之類。

符號集是我們對ipa文件進(jìn)行打包之后,和.app文件同級的后綴名為.dSYM的文件,這個文件必須使用Xcode進(jìn)行打包才有。每一個.dSYM文件都有一個UUID,和.app文件中的UUID對應(yīng),代表著是一個應(yīng)用。而.dSYM文件中每一條崩潰信息也有一個單獨(dú)的UUID,用來和程序的UUID進(jìn)行校對。這些UUID一致時才可以解析出當(dāng)前APP的崩潰信息.

我們在Archive的時候會生成.xcarchive文件,然后顯示包內(nèi)容就能夠在里面找到.dsYM文件和.app文件。

所以 為了更好的分析崩潰原因,在每次上架APP的時候,應(yīng)該保留對應(yīng)的app文件和dsym文件。

當(dāng)獲得一份crash日志時,我們需要將初始展示的十六進(jìn)制地址等原始信息映射為源代碼級別的方法名稱和代碼行數(shù),使其對開發(fā)人員可讀。這個過程稱為符號化解析。要成功地符號化解析一份crash日志,我們需要有對應(yīng)的應(yīng)用程序二進(jìn)制文件以及符號(.dSYM)文件。

當(dāng)程序崩潰的時候,我們可以獲得到崩潰的錯誤堆棧,但是這個錯誤堆棧都是0x開頭的16進(jìn)制地址,需要我們使用Xcode自帶的symbolicatecrash工具來將.Crash和.dSYM文件進(jìn)行符號化,就可以得到詳細(xì)崩潰的信息。

Symbolicatecrash
Symbolicatecrash是Xcode自帶的一個分析工具,可以通過機(jī)器上的崩潰日志和應(yīng)用的.dSYM文件定位發(fā)生崩潰的位置,把crash日志中的地址替換成代碼相應(yīng)位置。

使用效果:

分析前:
Thread 0 name: Dispatch queue: com.apple.main-thread
Thread 0 Crashed:
0 CoreFoundation 0x3723b870 0x37180000 + 768112
1 CoreFoundation 0x37196648 0x37180000 + 91720
2 CoreFoundation 0x37181e90 0x37180000 + 7824
3 CoreFoundation 0x3718bb74 0x37180000 + 47988
4 CoreFoundation 0x3718ba8e 0x37180000 + 47758
5 UIKit 0x30f0f866 0x30f0a000 + 22630

分析后:
0 CoreFoundation 0x3723b870 ___forwarding___ + 136
1 CoreFoundation 0x37196648 _CF_forwarding_prep_0 + 40
2 CoreFoundation 0x37181e90 CFRetain + 76
3 CoreFoundation 0x3718bb74 +[__NSArrayI __new::] + 48
4 CoreFoundation 0x3718ba8e -[__NSPlaceholderArray initWithObjects:count:] + 294
5 UIKit 0x30f0f866 -[UIView(Hierarchy) _makeSubtreePerformSelector:withObject:withObject:copySublayers:] + 70

在這個路徑下你可以得到系統(tǒng)自帶的Symbolicatecrash,把它拷貝到指定的文件下

/Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources

獲取.dSYM文件

選中archive的版本右擊,選擇Show in Finder就可以選中archived 文件然后顯示包內(nèi)容,就可以找到dSYM文件了。


解析步驟

  • 我在解析崩潰信息的時候,首先在桌面上建立一個Crash文件夾,然后將.Crash、app、.dSYM、symbolicatecrash放在這個文件夾中。

Paste_Image.png

注意:這里的 .crash 必須是真機(jī)安裝的打包的那個 sometwo 產(chǎn)生的崩潰日志才行,運(yùn)行其他的版本產(chǎn)生的崩潰日志,以下的解析會失敗。

如何把這個打包的應(yīng)用安裝到測試機(jī)上呢?注意這里的應(yīng)用不是 ipa文件,而且這個手機(jī)也可以沒被加入到當(dāng)前的開發(fā)者賬號中。

手機(jī)連上 itunes,在itunes中打開 手機(jī)的應(yīng)用, 文件->添加到資料庫 把桌面是上的那個應(yīng)用添加進(jìn)入,再同步更新到測試機(jī)器中即可。

Paste_Image.png

如果你一直解析失敗,那么可能你的 .Crash、app、.dSYM、的UUID不一致,通過終端工具可以查看 app、 .dSYM文件的UUID:

cd到文件夾
dwarfdump --uuid Sometwo.app/Sometwo
dwarfdump --uuid Sometwo.app.dSYM
三者一致才能還原符號表。
Paste_Image.png

Paste_Image.png

由上圖可以看出三折的UUID是不一致的,所以會一直解析失敗,無法符號化 .Crash文件。

在終端中輸入以下命令, iOS002 換成你自己的用戶名稱

  • cd /Users/iOS002/Desktop/Cash/
  • export DEVELOPER_DIR="/Applications/XCode.app/Contents/Developer"
  • ./symbolicatecrash /Users/iOS002/Desktop/Cash/SomeTwo.crash /Users/iOS002/Desktop/Cash/SomeTwo.app.dSYM > Control_symbol.crash

一切正常的話這樣就完成了一個崩潰日志的解析工作。解析完成后會生成一個新的.Crash文件,這個文件中就是崩潰詳細(xì)信息。圖中紅色標(biāo)注的部分就是我們代碼崩潰的部分。

收集崩潰日志

獲取崩潰信息方式
在iOS中獲取崩潰信息的方式有很多,比較常見的是使用友盟、云測、百度、Crashlytics等第三方分析工具,或者自己收集崩潰信息并上傳公司服務(wù)器。下面列舉一些我們常用的崩潰分析方式:

  • 自己實現(xiàn)應(yīng)用內(nèi)崩潰收集,并上傳服務(wù)器。
  • 使用友盟、云測、百度、Crashlytics等第三方崩潰統(tǒng)計工具。

自己收集崩潰信息

蘋果給我們提供了異常處理的類,NSException類。這個類可以創(chuàng)建一個異常對象,也可以通過這個類獲取一個異常對象。這個類中我們最常用的還是一個獲取崩潰信息的C函數(shù),我們可以通過這個函數(shù)在程序發(fā)生異常的時候收集這個異常。然后把收集到的崩潰信息發(fā)送到自己的服務(wù)器。

我們也可以通過下面方法獲取崩潰統(tǒng)計的函數(shù)指針:

 NSUncaughtExceptionHandler *handler = NSGetUncaughtExceptionHandler();
 NSSetUncaughtExceptionHandler (&UncaughtExceptionHandler);
//  收集崩潰信息的調(diào)用方法
void UncaughtExceptionHandler(NSException *exception) {
    NSArray *arr = [exceptioncallStackSymbols];//得到當(dāng)前調(diào)用棧信息
    NSString *reason = [exceptionreason];//非常重要,就是崩潰的原因
    NSString *name = [exceptionname];//異常類型
    NSLog(@"exception type : %@ \n crash reason : %@ \n call stack info : %@", name, reason, arr);
}

獲取到了崩潰的發(fā)送給開發(fā)者有以下兩種方式:

  1. 將崩潰信息持久化在本地,下次程序啟動時,將崩潰信息作為日志發(fā)送給開發(fā)者。

  2. 通過郵件發(fā)送給開發(fā)者。不過此種方式需要得到用戶的許可,因為iOS不能后臺發(fā)送短信或者郵件,會彈出發(fā)送郵件的界面,只有用戶點(diǎn)擊了發(fā)送才可發(fā)送。不過,此種方式最符合蘋果的以用戶至上的原則。

    發(fā)送郵件代碼:
    NSString *crashLogInfo = [NSString stringWithFormat:@"exception type : %@ \n crash reason : %@ \n callstack info : %@", name, reason, arr];
    NSString*urlStr = [NSString stringWithFormat:@"mailto://tianranwuwai@yeah.net?subject=bug報告&body=感謝您的配合! 錯誤詳情:%@",crashLogInfo];
    NSURL *url =[NSURL URLWithString:[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    [[UIApplication sharedApplication] openURL:url];
    

我們是否也可以在程序崩潰時,將崩潰信息寫入本地,APP再次啟動時,將崩潰信息上傳到我們的服務(wù)器。這里就要用到apple的一個函數(shù):NSSetUncaughtExceptionHandler。上代碼:

   application didFinishLaunchingWithOptions中調(diào)用
   [self catchCrashLogs];

    - (void)catchCrashLogs{
        NSSetUncaughtExceptionHandler(&UncaughtExceptionHandler);
    }
  
   void UncaughtExceptionHandler(NSException *exception){
      if (exception ==nil)return;
        NSArray *array = [exception callStackSymbols];
        NSString *reason = [exception reason];
        NSString *name  = [exception name];
        NSDictionary *dict = @{@"appException":@{@"exceptioncallStachSymbols":array,@"exceptionreason":reason,@"exceptionname":name}};
    if([SDFileToolClass writeCrashFileOnDocumentsException:dict]){
        NSLog(@"Crash logs write ok!");
    }
   }
    //寫入緩存中: 以下提供三個API,分別是:寫入,獲取,清空
    NSString * const SDCrashFileDirectory = @"SDMapHomeCrashFileDirectory"; //你的項目中自定義文件夾名
    + (NSString *)sd_getCachesPath{
        return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
    }
    + (BOOL)writeCrashFileOnDocumentsException:(NSDictionary *)exception{
    NSString *time = [[NSDate date] formattedDateWithFormat:@"yyyyMMddHHmmss" locale:[NSLocale currentLocale]];
    NSDictionary *infoDictionary = [[NSBundle mainBundle] infoDictionary];
    NSString *crashname = [NSString stringWithFormat:@"%@_%@Crashlog.plist",time,infoDictionary[@"CFBundleName"]];
    NSString *crashPath = [[self sd_getCachesPath] stringByAppendingPathComponent:SDCrashFileDirectory];
    NSFileManager *manager = [NSFileManager defaultManager];
    //設(shè)備信息
    NSMutableDictionary *deviceInfos = [NSMutableDictionary dictionary];
    [deviceInfos setObject:[infoDictionary objectForKey:@"DTPlatformVersion"] forKey:@"DTPlatformVersion"];
    [deviceInfos setObject:[infoDictionary objectForKey:@"CFBundleShortVersionString"] forKey:@"CFBundleShortVersionString"];
    [deviceInfos setObject:[infoDictionary objectForKey:@"UIRequiredDeviceCapabilities"] forKey:@"UIRequiredDeviceCapabilities"];

    BOOL isSuccess = [manager createDirectoryAtPath:crashPath withIntermediateDirectories:YES attributes:nil error:nil];
    if (isSuccess) {
        NSLog(@"文件夾創(chuàng)建成功");
        NSString *filepath = [crashPath stringByAppendingPathComponent:crashname];
        NSMutableDictionary *logs = [NSMutableDictionary dictionaryWithContentsOfFile:filepath];
        if (!logs) {
            logs = [[NSMutableDictionary alloc] init];
        }
        //日志信息
        NSDictionary *infos = @{@"Exception":exception,@"DeviceInfo":deviceInfos};
        [logs setObject:infos forKey:[NSString stringWithFormat:@"%@_crashLogs",infoDictionary[@"CFBundleName"]]];
        BOOL writeOK = [logs writeToFile:filepath atomically:YES];
        NSLog(@"write result = %d,filePath = %@",writeOK,filepath);
        return writeOK;
    }else{
        return NO;
      }
    }
    + (nullable NSArray *)sd_getCrashLogs{
     NSString *crashPath = [[self sd_getCachesPath] stringByAppendingPathComponent:SDCrashFileDirectory];
     NSFileManager *manager = [NSFileManager defaultManager];
     NSArray *array = [manager contentsOfDirectoryAtPath:crashPath error:nil];
     NSMutableArray *result = [NSMutableArray array];
    if (array.count == 0) return nil;
    for (NSString *name in array) {
        NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:[crashPath stringByAppendingPathComponent:name]];
        [result addObject:dict];
    }
    return result;
    }

    + (BOOL)sd_clearCrashLogs{
     NSString *crashPath = [[self sd_getCachesPath] stringByAppendingPathComponent:SDCrashFileDirectory];
    NSFileManager *manager = [NSFileManager defaultManager];
    if (![manager fileExistsAtPath:crashPath]) return YES; //如果不存在,則默認(rèn)為刪除成功
    NSArray *contents = [manager contentsOfDirectoryAtPath:crashPath error:NULL];
    if (contents.count == 0) return YES;
    NSEnumerator *enums = [contents objectEnumerator];
    NSString *filename;
    BOOL success = YES;
    while (filename = [enums nextObject]) {
        if(![manager removeItemAtPath:[crashPath stringByAppendingPathComponent:filename] error:NULL]){
            success = NO;
            break;
        }
    }
    return success;
    }

使用工具Crashlytics統(tǒng)計Crash

市場上有多種移動應(yīng)用Crash收集工具, 如友盟,MTJ等。在iOS中, 收集Crash主要通過兩種方式, 一種是信號量機(jī)制,因為crash通常會發(fā)出信號量,標(biāo)明某某應(yīng)用崩潰了, 另一種方式是每一個應(yīng)用都有一個crash handle, 即崩潰鉤子, 每當(dāng)程序崩潰時, 都會執(zhí)行這個回調(diào)。信號量比起崩潰句柄的區(qū)別有點(diǎn)像ios開發(fā)中的通知和delegate。 信號量拋出后,可以被多個捕獲crash的工具獲取到,然后取當(dāng)前的堆棧信息, 再利用該堆棧信息與原app的dsym文件進(jìn)行比對, 就可以找到崩潰的代碼行。
理論上講, 這個信號量機(jī)制優(yōu)秀于crash句柄, 因為這樣的話,可以有多個收集工具并行收集, 前提是,每個收集工具收集后,繼續(xù)拋出這個異常,而不是截斷這個異常,當(dāng)截斷后后續(xù)的其它工具就收集不到這個異常了, 會導(dǎo)致其它工具收集不全的問題。 而友盟正是這樣做的.
利用crash 句柄這種方式使得crash信息只能被一個收集工具所收集到,因為句柄只有一個。如果一個應(yīng)用中有多個收集工具都設(shè)置了這個句柄, 這里就得看誰最后設(shè)置這個句柄, 誰就有效。

上面是收集crash的方式說明, 現(xiàn)在說說Crashlytics這個工具。 原理和上面的一樣。 不一樣的是, 這個工具被twitter收購, 既然有這么一根大樹, 那就保證了這個工具的穩(wěn)定性。 所以建議使用, 目前是免費(fèi)的。
使用步驟基本上可以分為如下:

  • 注冊,
  • 收到邀請信, 然后一步步按其說明完成注冊。
  • 根據(jù)其提示,下載一個mac app配合進(jìn)行使用。
  • 當(dāng)有崩潰發(fā)生時,會給注冊的郵件發(fā)送崩潰統(tǒng)計,方便查看。

在crash信息收集時, 如果正在進(jìn)行debug調(diào)試,是收集不到信息的。

使用Crashlytics的好處:

  • Crashlytics不會漏掉任何應(yīng)用崩潰信息(就這兩個字讓我決定使用crashlytics)
  • Crashlytics可以象Bug管理工具那樣,管理這些崩潰日志, 可以根據(jù)頻率及影響用戶量來自動設(shè)置優(yōu)先級
  • 可以每天和每周將崩潰信息匯總發(fā)送到郵箱中。

具體使用,可以參照這篇文章Crashlytics

小結(jié)

有關(guān)應(yīng)用Crash的處理工作任重而道遠(yuǎn),后續(xù)會持續(xù)更新,先寫這些吧。


本文參考文章:
關(guān)于崩潰日志解讀很詳細(xì)很棒的的一篇文章
iOS被開發(fā)者遺忘在角落的NSException-其實它很強(qiáng)大
iOS崩潰調(diào)試(收集不同用戶的崩潰信息)

Paste_Image.png

Paste_Image.png

模擬器打印不出來 malloc stock的信息,需要真機(jī)。

1.unrecognized seletor。錯誤:這種情況很簡單,給一個對象發(fā)送了一條它不認(rèn)識的消息。比如說你的.h中聲明了某一個方法,但是.m中卻沒有實現(xiàn),而且你沒有對異常消息處理(消息轉(zhuǎn)發(fā))就會造成這種現(xiàn)象。解決辦法:首先排查自己的某一些方法是否實現(xiàn),其次看一下哪些對象接收了它不該接收的消息。

2.index 1 beyond NSArraMu [0,0]數(shù)組越界:數(shù)組越界這個不多說。

3.NSNul length 這個異常以可以歸類為第一種,也是給某一個對象發(fā)送了不識別的消息。常見原因有:給UILabel對象設(shè)置了text,此時的text內(nèi)容為空字符串null,然后你在取text的length的時候就會拋出異常。

4.EXC_BAD_ACCESS異常:這種大多數(shù)是對象提前釋放,訪問了野指針的錯誤。解決辦法:排查所有聲明為weak對象的使用,是否在沒有持有的情況下再次訪問了該對象(該對象已經(jīng)被釋放),第二在MRC情況下,排查一下所以已經(jīng)release的對象(聲明一點(diǎn),MRC中全局變量最好在dealloc方法中進(jìn)行釋放),第三排查一下所有block,是否block被正常賦值等。

5.崩潰在main函數(shù)。這種情況最苦逼也是最難找到bug所在,這種情況下,用@try @catch將main函數(shù)包裹起來,這樣會拋出異常堆棧信息等,或者通過添加全局breakPoint來追蹤bug。(扯淡)

@try @catch 是最后的大招

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

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

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