技 術(shù) 文 章 / 超 人
有需要研究或者需要使用防崩潰工具的可以導(dǎo)入我自己的寫(xiě)的Automatic Defense Crash Tool 開(kāi)源庫(kù)
在實(shí)際工作中希望在應(yīng)用上線后汽纠,能看見(jiàn)應(yīng)用運(yùn)行情況,崩潰日志帜乞。有一下幾種方式
收集崩潰日志
最直接的就是自己寫(xiě)一個(gè)崩潰收集方法捡多,每一個(gè)應(yīng)用只能申請(qǐng)一個(gè)崩潰收集監(jiān)聽(tīng)蓖康,當(dāng)接收到崩潰信息后,將崩潰信息保存在應(yīng)用中垒手,等待下次用戶啟動(dòng)應(yīng)用的時(shí)候把崩潰信息發(fā)送到自己的服務(wù)器蒜焊,來(lái)查看。
注意:自己收集崩潰日志需要注意科贬,每個(gè)應(yīng)用只能申請(qǐng)一個(gè)監(jiān)聽(tīng)崩潰事件泳梆,如果申請(qǐng)多個(gè),那么只有最后一個(gè)申請(qǐng)監(jiān)聽(tīng)崩潰事件的方法能收到信息榜掌。異常捕獲有2種优妙,一種是EXC_BAD_ACCESS引發(fā)崩潰,一種是程序向自身發(fā)送了SIGABRT信號(hào)而崩潰唐责。
步驟1:首先需要在程序的入口處加入異常捕獲的注冊(cè)。一般都是在程序啟動(dòng)事注冊(cè)
//MyAccessHandleException 是收集EXC_BAD_ACCESS崩潰時(shí)系統(tǒng)回調(diào)的C方法名稱(chēng)
//MySignalExceptionHandler 是收集signal崩潰時(shí)系統(tǒng)回調(diào)的C方法名稱(chēng)
NSSetUncaughtExceptionHandler(&MyAccessHandleException);
signal(SIGHUP, MySignalExceptionHandler);
signal(SIGINT, MySignalExceptionHandler);
signal(SIGQUIT, MySignalExceptionHandler);
signal(SIGABRT, MySignalExceptionHandler);
signal(SIGILL, MySignalExceptionHandler);
signal(SIGSEGV, MySignalExceptionHandler);
signal(SIGFPE, MySignalExceptionHandler);
signal(SIGBUS, MySignalExceptionHandler);
signal(SIGPIPE, MySignalExceptionHandler);
步驟2:實(shí)現(xiàn)回調(diào)的C方法
void MyAccessHandleException(NSException *exception)
{
// 異常的堆棧信息
NSArray *stackArray = [exception callStackSymbols];
// 出現(xiàn)異常的原因
NSString *reason = [exception reason];
// 異常名稱(chēng)
NSString *name = [exception name];
//這里拿到了崩潰原因和異常名稱(chēng)后瘾带,就把信息保存在本地鼠哥,等待下次啟動(dòng)程序的時(shí)候把信息發(fā)送到服務(wù)器熟菲,因?yàn)楫惓2东@是在主線程中完成的,所以在獲取到異常崩潰信息后不能立馬把信息發(fā)送到服務(wù)器朴恳,因?yàn)榫W(wǎng)絡(luò)請(qǐng)求是異步的抄罕,主線程會(huì)直接崩潰,網(wǎng)絡(luò)請(qǐng)求發(fā)送不出去.
}
#pragma mark - signal收集
void MySignalExceptionHandler(int signal)
{
//組裝異常信息的可變字符串
NSMutableString *mstr = [[NSMutableString alloc] init];
[mstr appendString:@"Stack:\n"];
void* callstack[128];
int i, frames = backtrace(callstack, 128);
char** strs = backtrace_symbols(callstack, frames);
for (i = 0; i <frames; ++i) {
[mstr appendFormat:@"%s\n", strs[i]];
}
//這里拿到了崩潰信息后于颖,就把信息保存在本地呆贿,等待下次啟動(dòng)程序的時(shí)候把信息發(fā)送到服務(wù)器
}
當(dāng)然也可以寫(xiě)一個(gè)卡死線程來(lái)阻止程序崩潰,先發(fā)送網(wǎng)絡(luò)請(qǐng)求在卡死線程森渐,等待網(wǎng)絡(luò)請(qǐng)求成功后做入,在取消卡死線程。
例如:
//獲取MainRunloop
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFArrayRef allModes = CFRunLoopCopyAllModes(runLoop);
while (!_netWorkSendSuccess){ //根據(jù)_netWorkSendSuccess標(biāo)記來(lái)判斷當(dāng)前是否需要繼續(xù)卡死線程同衣,可以在網(wǎng)絡(luò)請(qǐng)求成功后修改_netWorkSendSuccess的值竟块。
for (NSString *mode in (__bridge NSArray *)allModes) {
CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
}
}
//釋放對(duì)象
CFRelease(allModes);
接入第三方數(shù)據(jù)收集
給應(yīng)用接入相關(guān)數(shù)據(jù)收集的SDK,比如我自己寫(xiě)的龍淵統(tǒng)計(jì)SDK或者友盟耐齐、熱云等統(tǒng)計(jì)SDK浪秘。這些SDK把應(yīng)用的運(yùn)行崩潰等情況數(shù)據(jù)都會(huì)返在他們服務(wù)器上,而我們只需要在他們服務(wù)器上查看自己想要了解的部分?jǐn)?shù)據(jù)(為什么關(guān)于數(shù)據(jù)的收集這么簡(jiǎn)單埠况,但是大多數(shù)應(yīng)用游戲都不自己做耸携,而是接入第三方,那是因?yàn)榈谌讲粌H僅是收集數(shù)據(jù)辕翰,還提供了一套對(duì)收集的數(shù)據(jù)保存分析的處理夺衍,例如每年年末支付寶會(huì)提供自己一年的賬單,告訴你這一年你的錢(qián)這么花的金蜀,買(mǎi)什么類(lèi)型產(chǎn)品的比例最高等等)刷后。
Xcode Crashes工具
Xcode Crashes工具
如果用戶在設(shè)置手機(jī)的時(shí)候開(kāi)啟了“與應(yīng)用開(kāi)發(fā)者共享”功能,當(dāng)應(yīng)用崩潰的時(shí)候渊抄,應(yīng)用的崩潰信息會(huì)返回給蘋(píng)果尝胆,然后蘋(píng)果會(huì)將崩潰信息返回到相應(yīng)應(yīng)用信息里(蘋(píng)果返回給開(kāi)發(fā)者崩潰信息會(huì)有延遲,可能有1天左右的延遲)护桦。開(kāi)發(fā)者可以在打包發(fā)布該應(yīng)用的電腦的Xcode的Crashes工具中查看到信息含衔。這是蘋(píng)果官方說(shuō)明
IOS系統(tǒng)會(huì)在在每個(gè)應(yīng)用崩潰的時(shí)候把崩潰信息保存在手機(jī)本地,如果用戶沒(méi)有開(kāi)啟“與應(yīng)用開(kāi)發(fā)者共享”功能二庵,我們可通過(guò)電腦鏈接用戶手機(jī)通過(guò)Xcode的 Window --> Devices and Simulators -- > 選擇需要查看的手機(jī) --> View Device Log,查看自己想要看的應(yīng)用的crash內(nèi)容
蘋(píng)果審核反饋的崩潰日志
在應(yīng)用發(fā)布給蘋(píng)果審核的時(shí)候贪染,如果測(cè)出來(lái)應(yīng)用有崩潰的情況,蘋(píng)果會(huì)把崩潰信息返回給我們(會(huì)返回一個(gè).crash和tns.mhfyxlpb.crash_symbolicated文件)催享,但是蘋(píng)果返回的信息是進(jìn)行了十六進(jìn)制轉(zhuǎn)換的地址杭隙,其他人拿著這個(gè)崩潰日志并看不出任何頭緒。只有使用發(fā)布該應(yīng)該事打包的dSYM文件才能知道具體在代碼中崩潰的地方因妙。因?yàn)閐SYM文件痰憎、app票髓、.crash文件中有統(tǒng)一的UDID。只有相同的才能正確的解析出崩潰的位置铣耘。
看解析蘋(píng)果返回的.crash文件的方法有2種
-
方法1:使用dSYM解析(官方)
.dSYM文件有一個(gè)UUID洽沟,和.app文件中的UUID相對(duì)應(yīng),代表著是一個(gè)應(yīng)用蜗细。而.dSYM文件中每一條崩潰信息也有一個(gè)單獨(dú)的UUID裆操,用來(lái)和程序的UUID進(jìn)行校對(duì)。.dSYM中存儲(chǔ)著文件名炉媒、方法名踪区、行號(hào)的信息,是和可執(zhí)行文件的16進(jìn)制函數(shù)地址對(duì)應(yīng)的橱野,通過(guò)分析崩潰的.Crash文件可以準(zhǔn)確知道具體的崩潰信息朽缴。
注意,以下情況不會(huì)有崩潰信息產(chǎn)生 |
---|
內(nèi)存訪問(wèn)錯(cuò)誤(不是野指針錯(cuò)誤) |
低內(nèi)存水援,當(dāng)程序內(nèi)存使用過(guò)多會(huì)造成系統(tǒng)低內(nèi)存的問(wèn)題密强,系統(tǒng)會(huì)將程序內(nèi)存回收 |
因?yàn)槟撤N原因觸發(fā)看門(mén)狗機(jī)制 |
步驟1:首先要確保應(yīng)用在打包的時(shí)候設(shè)置了DWARF with dSYM File,查看Xcode --> Build Setting 搜索 "debug information format"查看是否為DWARF with dSYM File蜗元。如果不是請(qǐng)修改或渤。這樣打包后就會(huì)產(chǎn)生dSYM文件
步驟2:先在桌面任意創(chuàng)建一個(gè)文件夾
步驟3:然后在Finder-->應(yīng)用程序 --> 中找到Xcode右鍵顯示中點(diǎn)擊顯示包內(nèi)容
--> SharedFrameworks --> DVTFoundation.framework --> Versions --> A --> Resources --> 找到symbolicatecrash文件,把它復(fù)制步驟2創(chuàng)建的文件夾中奕扣。
步驟4:在Xcode --> Window --> Organizer --> 選擇 Archives --> 選擇打包的應(yīng)用 --> 選擇Download dSYM (或者直接在Archives頁(yè)面對(duì)應(yīng)的包右鍵選擇show in finder找到dSYM文件)薪鹦。 把dSYM文件復(fù)制到步驟2創(chuàng)建的文件夾中
步驟5:把蘋(píng)果反饋的.crash文件復(fù)制到步驟2創(chuàng)建的文件夾中
這個(gè)時(shí)候你的文件夾中應(yīng)該有3個(gè)文件
步驟6:打開(kāi)終端 CD到步驟2創(chuàng)建的文件夾下
步驟7:在終端中輸入(attachment-1873276475927892228CrashLogsCollectorWorker-8390930539917002046.crash是蘋(píng)果返回的.crash文件名 cytus2.app.dSYM是審核的包對(duì)應(yīng)的dSYM文件 ,MyCrash.log是解析后結(jié)果返回的文件 )
./symbolicatecrash ./attachment-1873276475927892228CrashLogsCollectorWorker-8390930539917002046.crash ./cytus2.app.dSYM > MyCrash.log
注意:如果DEVELOPER_DIR惯豆,請(qǐng)?jiān)诮K端中先輸入export DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer池磁。在執(zhí)行步驟7.
步驟8:文件夾中應(yīng)該已經(jīng)有了MyCrash.crash文件,這個(gè)就是我們解析出來(lái)的文件楷兽。打開(kāi)就可以看見(jiàn)詳細(xì)的錯(cuò)誤內(nèi)容
5叵ā!芯杀!注意:網(wǎng)上很多人說(shuō)如果不使用打包時(shí)對(duì)應(yīng)的dSYM文件會(huì)造成分析出來(lái)的位置不正確端考。可是我試過(guò)后發(fā)現(xiàn)揭厚,如果不是打包是對(duì)應(yīng)的dSYM文件根本分析不出來(lái)却特。可能是蘋(píng)果后門(mén)做了修改
-
方法2:不使用dSYM文件
之所以需要用dSYM來(lái)解析.crash文件是因?yàn)閄code會(huì)在打包成ipa文件時(shí)除去Symbol Table(符號(hào)表)的非系統(tǒng)符號(hào)部分筛圆。導(dǎo)致地址無(wú)法對(duì)應(yīng)函數(shù)名裂明。這個(gè)方法是在網(wǎng)上看見(jiàn)大牛(iOS符號(hào)表恢復(fù)&逆向支付寶)提供的,我測(cè)試過(guò)確實(shí)可行太援,但注意其中但app包必須是審核的app包闽晦,應(yīng)為包中的UDID無(wú)法對(duì)應(yīng)會(huì)造成無(wú)法解析轰绵。
步驟1:查看蘋(píng)果返回的.crash日志中的Code Type
,是ARM-64說(shuō)明它的結(jié)構(gòu)是arm64(確定結(jié)構(gòu)是armv7尼荆、armv7s、arm64中的哪一種)
步驟2:查看Triggered by Thread
是 0 唧垦,說(shuō)明是0號(hào)線程崩潰的捅儒,那么就應(yīng)該查看Thread 0 Crashed:
步驟3:在Thread 0 Crashed:
查看具體錯(cuò)誤的棧地址
棧:是崩潰時(shí)調(diào)用的棧地址(虛擬內(nèi)存地址,棧地址 = 基地址 + 偏移 )
基地址:基地址指向的地址是應(yīng)用中這個(gè)模塊加載到內(nèi)存中的起始地址
偏移:十進(jìn)制的偏移量
步驟4:找到Binary Images
,查看具體崩潰app的UUID振亮。
步驟5:在打包的Xcode工程中找到對(duì)應(yīng)的app文件show in finder巧还,并用終端cd到該文件的位置。
步驟6:在終端中輸入dwarfdump --uuid cytus2.app/cytus2
點(diǎn)擊回車(chē)坊秸,會(huì)得到UUID麸祷,確認(rèn)這里的UUID和剛剛步驟4查看的UUID是否相同,如果相同則說(shuō)明是對(duì)應(yīng)的app(cytus2是自己app的名稱(chēng))
步驟7:在終端中輸入atos -o cytus2.app/cytus2 -arch arm64 0x0000000100dd1be4
回車(chē)會(huì)得到該棧地址具體在代碼中的位置褒搔。這樣就找到了代碼出錯(cuò)的地方(cytus2是應(yīng)用名稱(chēng) arm64是步驟1查看到的結(jié)構(gòu)阶牍, 0x0000000100dd1be4是步驟3查看到的自己應(yīng)用名下報(bào)錯(cuò)的棧地址)
.Crash文件結(jié)構(gòu)說(shuō)明
.crash文件中對(duì)應(yīng)的名稱(chēng) | 說(shuō)明 |
---|---|
Incident Idnetifier | 崩潰報(bào)告的唯一標(biāo)識(shí)符,不同的Crash |
CrashReporter Key | 設(shè)備標(biāo)識(shí)相對(duì)應(yīng)的唯一鍵值(并非真正的設(shè)備的UDID星瘾,蘋(píng)果為了保護(hù)用戶隱私iOS6以后已經(jīng)無(wú)法獲取)走孽。通常同一個(gè)設(shè)備上同一版本的App發(fā)生Crash時(shí),該值都是一樣的琳状。 |
Hardware Model | 代表發(fā)生Crash的設(shè)備類(lèi)型磕瓷,上圖中的“iPad4,4”代表iPad Air,一般蘋(píng)果審核都用的ipad |
Process | 代表Crash的進(jìn)程名稱(chēng)念逞,通常都是我們的App的名字, []里面是當(dāng)時(shí)進(jìn)程的ID |
Path | 可執(zhí)行程序在手機(jī)上的存儲(chǔ)位置困食,注意路徑時(shí)到XXX.app/XXX,XXX.app其實(shí)是作為一個(gè)Bundle的翎承,真正的可執(zhí)行文件其實(shí)是Bundle里面的XXX硕盹,感興趣的可以自己查一下相關(guān)資料 |
Identifier | 你的App的Indentifier,通常為“com.xxx.yyy”审洞,xxx代表你們公司的域名莱睁,yyy代表某一個(gè)App |
Version | 當(dāng)前App的版本號(hào),由Info.plist中的兩個(gè)字段組成芒澜,CFBundleShortVersionString and CFBundleVersion |
Code Type | 當(dāng)前App的CPU架構(gòu) |
Parent Process | 當(dāng)前進(jìn)程的父進(jìn)程仰剿,由于iOS中App通常都是單進(jìn)程的,一般父進(jìn)程都是launchd |
Date/Time | Crash發(fā)生的時(shí)間痴晦,可讀的字符串 |
OS Version | 系統(tǒng)版本南吮,()內(nèi)的數(shù)字代表的時(shí)Bulid號(hào) |
Report Version | Crash日志的格式,目前基本上都是104誊酌,不同的version里面包含的字段可能有不同 |
Exception Type | 異常類(lèi)型 |
Exception Subtype | 異常子類(lèi)型 |
Crashed Thread | 發(fā)生異常的線程號(hào) |
Thread Backtrace | 發(fā)生Crash的線程的Crash調(diào)用棧部凑,從上到下分別代表調(diào)用順序露乏,最上面的一個(gè)表示拋出異常的位置,依次往下可以看到API的調(diào)用順序涂邀。上圖的信息表明本次Crash出現(xiàn)xxxViewController的323行瘟仿,出錯(cuò)的函數(shù)調(diào)用為orderCountLoadFailed。 |
Thread State | Crash時(shí)發(fā)生時(shí)刻比勉,線程的狀態(tài)劳较,通常我們根據(jù)Crash棧即可獲取到相關(guān)信息,這部分一般不用關(guān)心浩聋。 |
Binary Images | Crash時(shí)刻App加載的所有的庫(kù)观蜗,其中第一行是Crash發(fā)生時(shí)我們App可執(zhí)行文件的信息,可以看出為armv7衣洁,可執(zhí)行文件的包得uuid位c0f……cd65墓捻,解析Crash的時(shí)候dsym文件的uuid必須和這個(gè)一樣才能完成Crash的符號(hào)化解析。 |
網(wǎng)上還有一種解析.Crash文件的方法坊夫,是給Xcode添加插件的方式砖第,本人沒(méi)有用過(guò),列出來(lái)大家參考下
程序穩(wěn)定性防止崩潰
一般程序崩潰都是存在與對(duì)象打交道時(shí)發(fā)生異常情況环凿,程序無(wú)法處理導(dǎo)致的厂画。
就好比消息轉(zhuǎn)發(fā),當(dāng)調(diào)用一個(gè)類(lèi)或?qū)ο蟮姆椒〞r(shí)拷邢,系統(tǒng)會(huì)發(fā)送一個(gè)Send_Message消息袱院。系統(tǒng)會(huì)在該類(lèi)的方法鏈表中去查找是否有該方法,判斷程序是否能響應(yīng)該方法瞭稼。如果不能忽洛,系統(tǒng)會(huì)在該類(lèi)中去生成方法簽名,如果能生成方法簽名环肘,就完成消息轉(zhuǎn)發(fā)哩俭,如果不能生成就會(huì)調(diào)用在
- (void)doesNotRecognizeSelector:(SEL)aSelector
方法妻往。從而crash。為什么會(huì)調(diào)用這個(gè)方法呢,因?yàn)槲覀兛梢酝ㄟ^(guò)在這個(gè)方法中做處理纸泡,來(lái)避免crash窗看。程序之所以崩潰莲组,是因?yàn)榘l(fā)生的情況讓系統(tǒng)無(wú)法處理情组。所以我們只要對(duì)相應(yīng)的異常情況做出相應(yīng)處理。這樣就能防止程序崩潰了益涧。
NSArray锈锤、NSDictionary、NSMutableArray、NSMutableDictionary
的異常處理
NSMutableDictionary
NSMutableDictionary *dic = [NSMutableDictionary new];
//存值的時(shí)候如果值是nil的系統(tǒng)會(huì)直接crash
[dic setObject:nil forKey:@"123"];
//所以我們需要為NSMutableDictionary寫(xiě)一個(gè)category久免,添加一個(gè)方法浅辙,對(duì)存值進(jìn)行判斷
- (void)safeSetObject:(id _Nonnull)object forKey:(NSString * _Nonnull)key
{
if(!object)
{
return;
}
if(object == [NSNull class] || object == NULL )
{
return;
}
if(!key)
{
return;
}
if([key stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]].length == 0)
{
return;
}
[self setObject:object forKey:key];
}
上面的category方法主要用于添加對(duì)象為object時(shí),當(dāng)加入字典對(duì)象固定為NSString類(lèi)型時(shí)阎姥,可以直接用系統(tǒng)的方法- (void)setValue:(nullable ObjectType)value forKey:(NSString *)key
记舆。該方法內(nèi)部會(huì)自動(dòng)判斷value是否為空,為空時(shí)不會(huì)存入值到字典中呼巴。但僅適用于NSString類(lèi)型氨淌。但該方法還是需要對(duì)Key的值進(jìn)行判斷
NSDictionary
id objetc1 = @"123";
id objetc2 = nil;
id objetc3 = nil;
id objetc4 = [NSMutableDictionary new];
NSString *key1 = nil;
NSString *key2 = @"12";
NSString *key3 = @"34";
NSString *key4 = nil;
//創(chuàng)建NSDictionary的時(shí)候會(huì)直接crash,因?yàn)橛衯alue或者ke 為nil
NSDictionary *dic = @{key1:objetc1,
key2:objetc2,
key3:objetc3,
key4:objetc4,
};
//所以我們需要一個(gè)方法來(lái)保證加入的value和key 不為nil
//檢查每個(gè)value
//如果該value為nil并沒(méi)有傳入class時(shí)伊磺,返回空字符串。保證程序不會(huì)崩潰
//如果該value為nil并傳入class時(shí)删咱,可以根據(jù)傳入的class 來(lái)創(chuàng)建對(duì)應(yīng)class對(duì)象屑埋。
//既保證程序不會(huì)因?yàn)閚il的value而crash,也不會(huì)在后面取值時(shí)因?yàn)槿〕龅膶?duì)象類(lèi)型不對(duì)而造成操作錯(cuò)誤痰滋。
- (id)checkObject:(id _Nonnull)object forClassValue:(Class)class
{
if(!object)
{
if (class) {
return [class new];
}else
{
return @"";
}
}
if(object == [NSNull class] || object == NULL )
{
if (class) {
return [class new];
}else
{
return @"";
}
}
return object;
}
//對(duì)于Key的判斷
- (NSString *)checkString:(NSString *_Nonnull)string
{
if (![string isKindOfClass:[NSString class]]) {
//stringByTrimmingCharactersInSet是NSString方法摘能,如果key原本不是string類(lèi)型調(diào)用該方法會(huì)crash
return @"";
}
if (!string) {
return @"";
}
if(string == [NSNull class] || string == NULL )
{
return @"";
}
if ([string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]].length == 0) {
return @"";
}
return string;
}
NSMutableArray
NSMutableArray *array = [NSMutableArray new];
NSString *s = nil;
//添加s時(shí)會(huì)crash 因?yàn)闉閚il 同Dictionary一樣。需要對(duì)添加的對(duì)象進(jìn)行判斷敲街。
[array addObject:s];
對(duì)于NSMutableArray
团搞、NSMutableDictionary
取值。盡量都要做相應(yīng)的轉(zhuǎn)換多艇。一般情況下服務(wù)端傳過(guò)來(lái)的數(shù)據(jù)類(lèi)型都是協(xié)商好的逻恐。但要抱有對(duì)返回?cái)?shù)據(jù)懷疑的態(tài)度去做判斷處理。增加應(yīng)用的健壯性
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSMutableDictionary *dic = (NSMutableDictionary *)responseObject;
//當(dāng)服務(wù)端傳過(guò)來(lái)的type對(duì)象的類(lèi)型本身就是string的時(shí)候峻黍,就會(huì)crash
//因?yàn)閇dic objectForKey:@"type"]本身的類(lèi)型就是 NSString,而NSString沒(méi)有 stringValue這個(gè)方法复隆。系統(tǒng)找不到stringValue方法消息轉(zhuǎn)發(fā)失敗,所以就會(huì)crash
NSString *type = [[dic objectForKey:@"type"] stringValue];
//因此我們?cè)陬?lèi)型轉(zhuǎn)換的時(shí)候要判斷類(lèi)型
if([dic objectForKey:@"type"] && ![[dic objectForKey:@"type"] isKindOfClass:[NSString class]])
{
NSString *type = [[dic objectForKey:@"type"] stringValue];
}
//一個(gè)接口有多個(gè)參數(shù)姆涩,就要判斷多次挽拂。這樣即使代碼臃腫也讓代碼執(zhí)行效率降低,不美觀
//正常情況下都是與服務(wù)端同事協(xié)商好骨饿,每個(gè)接口參數(shù)的類(lèi)型或者一開(kāi)始就協(xié)商好所有數(shù)據(jù)都傳string亏栈。
//但也會(huì)有比較坑的同事,啥都沒(méi)有宏赘,而且數(shù)據(jù)類(lèi)型還一直變绒北。很尷尬的(特別是開(kāi)發(fā)測(cè)試階段)
//有時(shí)服務(wù)端會(huì)考慮到如果從數(shù)據(jù)庫(kù)中取出來(lái)數(shù)據(jù)后在轉(zhuǎn)換一次數(shù)據(jù)類(lèi)型,會(huì)讓效率降低察署。
//所以我們最好是直接把服務(wù)端返回的所有數(shù)據(jù)直接轉(zhuǎn)成NSString镇饮。
//這樣自己使用的時(shí)候根據(jù)情況自行轉(zhuǎn)換。就不用擔(dān)心服務(wù)端出什么毛病了
//最為直接的就是用stringWithFormat直接轉(zhuǎn),不用考慮用%@ 還是%ld
NSString *type = [NSString stringWithFormat:@"%@",[dic objectForKey:@"type"]];
};
對(duì)于Array
數(shù)組來(lái)說(shuō)储藐,在插入對(duì)象或者根據(jù)下坐標(biāo)取值時(shí)俱济,如果超過(guò)數(shù)組長(zhǎng)度程序也會(huì)crash,因?yàn)閿?shù)組越界了钙勃。最常見(jiàn)的就是在使用TableView等控件事蛛碌,數(shù)據(jù)源是Array,取數(shù)據(jù)的時(shí)候越界辖源。
很多時(shí)候涂方便使用self.tableArray[indexPath.row]
來(lái)取數(shù)據(jù)蔚携。這樣是不好的。應(yīng)該使用方法去取值克饶。這樣能在方法中進(jìn)行判斷防止crash酝蜒。寫(xiě)一個(gè)Array的category來(lái)防止數(shù)據(jù)越界
- (id)safeObjectAtIndex:(NSUInteger)index
{
if (index >= [self count]) {
return nil;
}
id value = [self objectAtIndex:index];
if (value == [NSNull null]) {
return nil;
}
return value;
}
同上道理一樣,在對(duì)String等對(duì)象 insertString矾湃、substringToIndex亡脑、substringWithRange、substringFromIndex做插入截取等操作時(shí),也要判斷是否存在下標(biāo)越界的情況
NSMutableString *testString = [NSMutableString stringWithString:@"123456"];
if (testString.length >= 8) {
[testString insertString:@"-" atIndex:4];
[testString insertString:@"-" atIndex:7];
}
NSString *str = @"012345";
if (str.length > 8) {
[str substringFromIndex:8];
[str substringToIndex:8];
[str substringWithRange:NSMakeRange(5, 2)];
}
當(dāng)然還有一種比較少用的方法邀跃,就是@try@catch霉咨。可以在預(yù)測(cè)會(huì)發(fā)生崩潰的地方加入拍屑,來(lái)拋出異常途戒,防止崩潰
NSMutableDictionary *dic = [NSMutableDictionary new];
[dic setObject:@(1) forKey:@"123"];
@try
{
[[dic objectForKey:@"123"] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
}@catch(NSException *exception)
{
NSLog(@"發(fā)生了異常情況");
}
相關(guān)命令
1、獲取dSYM文件 UUID
//Demo.app.dSYM是你的dSYM文件名稱(chēng)
dwarfdump --uuid Demo.app.dSYM
注:如果你的app有多個(gè)架構(gòu)僵驰,那么會(huì)輸出不同架構(gòu)的多個(gè)uuid喷斋,你只需要根據(jù)崩潰日志里崩潰設(shè)備是什么架構(gòu),就選擇對(duì)應(yīng)架構(gòu)的uuid使用即可
2蒜茴、將app.dSYM文件壓縮成tgz
//Demo.tgz是對(duì)Demo.app.dSYM壓縮成tgz格式后的產(chǎn)物名稱(chēng)
//Linux 命令
tar -czvf Demo.tgz Demo.app.dSYM
3继准、解壓tgz文件
tar xzvf Demo.tgz