iOS Crash 常用分析方法

崩潰分析

崩潰日志(crash log)

根據(jù)符號表來監(jiān)測崩潰位置

  1. 什么是符號表

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

    .dSYM文件其實(shí)是一個(gè)目錄力崇,在子目錄中包含了一個(gè)16進(jìn)制的保存函數(shù)地址映射信息的中轉(zhuǎn)文件袍榆,所有Debug的symbols都在這個(gè)文件中(包括文件名最岗、函數(shù)名烙常、行號等),所以也稱之為調(diào)試符號信息文件召耘。

  2. 符號表有什么用

    符號表就是用來符號化 crash log(崩潰日志)百炬。crash log中有一些方法16進(jìn)制的內(nèi)存地址等,通過符號表就能找到對應(yīng)的能夠直觀看到的方法名之類污它。

  3. 如何得到.dsYM文件

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

Xcode中查看崩潰信息

Xcode->Window->Organizer->Crashes

img

崩潰日志分析

參考iOS應(yīng)用崩潰日志分析里面有很詳細(xì)的分析介紹衫贬。

img

以上是一個(gè)完整的崩潰日志德澈。

如何得到崩潰日志

  1. 把設(shè)備連上電腦,得到自己設(shè)備的崩潰日志

    崩潰日志可以從xcode里打開Devices看到對應(yīng)手機(jī)的一些崩潰信息固惯。點(diǎn)擊下圖的View Device Logs就能看到崩潰日志梆造。

    img
  2. 使用第三方崩潰管理工具

    我暫時(shí)只使用過友盟,友盟里面有錯(cuò)誤分析葬毫,就是截取的崩潰日志镇辉。

  3. 自己截取崩潰日志

    自己寫入代碼,然后截取到崩潰日志供常,把崩潰日志發(fā)送到開發(fā)者郵箱里摊聋。 iOS Crash(崩潰)調(diào)試技巧這篇文章中有介紹如何截取崩潰日志并發(fā)送到郵箱。

分析崩潰日志

  1. 崩潰日志中的(3)異常

    Exception Type: 異常類型 通常包含1.7中的Signal信號和EXC_BAD_ACCESS栈暇。

    Exception Codes: 異常編碼 0x8badf00d: 讀做 “ate bad food”! (把數(shù)字換成字母,是不是很像 :p)該編碼表示應(yīng)用是因?yàn)榘l(fā)生watchdog超時(shí)而被iOS終止的箍镜。 通常是應(yīng)用花費(fèi)太多時(shí)間而無法啟動(dòng)源祈、終止或響應(yīng)用系統(tǒng)事件煎源。

    0xbad22222: 該編碼表示 VoIP 應(yīng)用因?yàn)檫^于頻繁重啟而被終止。

    0xdead10cc: 讀做 “dead lock”!該代碼表明應(yīng)用因?yàn)樵诤笈_運(yùn)行時(shí)占用系統(tǒng)資源香缺,如通訊錄數(shù)據(jù)庫不釋放而被終止 手销。

    0xdeadfa11: 讀做 “dead fall”! 該代碼表示應(yīng)用是被用戶強(qiáng)制退出的。根據(jù)蘋果文檔, 強(qiáng)制退出發(fā)生在用戶長按開關(guān)按鈕直到出現(xiàn) “滑動(dòng)來關(guān)機(jī)”, 然后長按 Home按鈕图张。強(qiáng)制退出將產(chǎn)生 包含0xdeadfa11 異常編碼的崩潰日志, 因?yàn)榇蠖鄶?shù)是強(qiáng)制退出是因?yàn)閼?yīng)用阻塞了界面锋拖。

  2. 崩潰日志中的(4)線程回溯

    這部分提供應(yīng)用中所有線程的回溯日志。 回溯是閃退發(fā)生時(shí)所有活動(dòng)幀清單祸轮。它包含閃退發(fā)生時(shí)調(diào)用函數(shù)的清單兽埃。看下面這行日志:

    img

    它包括四列: 幀編號—— 此處是2适袜。(數(shù)子從大到小為發(fā)生的順序) 二進(jìn)制庫的名稱 ——此處是 XYZLib. 調(diào)用方法的地址 ——此處是 0x34648e88. 第四列分為兩個(gè)子列柄错,一個(gè)基本地址和一個(gè)偏移量。此處是0×83000 + 8740, 第一個(gè)數(shù)字指向文件苦酱,第二個(gè)數(shù)字指向文件中的代碼行售貌。

野指針分析方法 ( Enable Malloc Scribble )

因?yàn)橐爸羔樀脑虬l(fā)生崩潰是常常出現(xiàn)的事,而且比較隨機(jī)疫萤。關(guān)于一些原因及概念后面我們會講到颂跨。所以我們要提高野指針的崩潰率好來幫我們快速找到有問題的代碼。

對象釋放后只有出現(xiàn)被隨機(jī)填入的數(shù)據(jù)是不可訪問的時(shí)候才會必現(xiàn)Crash扯饶。

這個(gè)地方我們可以做一下手腳恒削,把這一隨機(jī)的過程變成不隨機(jī)的過程。對象釋放后在內(nèi)存上填上不可訪問的數(shù)據(jù)帝际,其實(shí)這種技術(shù)其實(shí)一直都有蔓同,xcode的Enable Scribble就是這個(gè)作用。

img

更加詳細(xì)的介紹可以參考:如何定位Obj-C野指針隨機(jī)Crash蹲诀。

僵尸模式 ( NSZombieEnabled )

啟用了NSZombieEnabled的話斑粱,它會用一個(gè)僵尸來替換默認(rèn)的dealloc實(shí)現(xiàn),也就是在引用計(jì)數(shù)降到0時(shí)脯爪,該僵尸實(shí)現(xiàn)會將該對象轉(zhuǎn)換成僵尸對象则北。僵尸對象的作用是在你向它發(fā)送消息時(shí),它會顯示一段日志并自動(dòng)跳入調(diào)試器痕慢。

所以當(dāng)啟用NSZombieEnabled時(shí)尚揣,一個(gè)錯(cuò)誤的內(nèi)存訪問就會變成一條無法識別的消息發(fā)送給僵尸對象。僵尸對象會顯示接受到得信息掖举,然后跳入調(diào)試器快骗,這樣你就可以查看到底是哪里出了問題。

所以這時(shí)一般崩潰的原因是:調(diào)用了已經(jīng)釋放的內(nèi)存空間,或者說重復(fù)釋放了某個(gè)地址空間方篮。

如何找出問題

  1. NSZombieEnabled

    打開NSZombieEnabled之后名秀,如果遇到對應(yīng)的崩潰類型既調(diào)用了已經(jīng)釋放的內(nèi)存空間,或者說重復(fù)釋放了某個(gè)地址空間藕溅。那么就能在GDB中看到對應(yīng)的輸出信息匕得。

    比如會出現(xiàn)如下這樣的問題: [__NSArrayM addObject:]: message sent to deallocated instance 0x7179910

  2. MallocStackLoggingNoCompact

    如果崩潰是發(fā)生在當(dāng)前調(diào)用棧,通過上面的做法巾表,系統(tǒng)就會把崩潰原因定位到具體代碼中汁掠。但是,如果崩潰不在當(dāng)前調(diào)用棧集币,系統(tǒng)就僅僅只能把崩潰地址告訴我們考阱,而沒辦法定位到具體代碼,這樣我們也沒法去修改錯(cuò)誤惠猿。這時(shí)就可以修改scheme羔砾,讓xcode記錄每個(gè)地址alloc的歷史,這樣我們就可以用命令把這個(gè)地址還原出來偶妖。 如圖:(跟設(shè)置 NSZombieEnabled 一樣姜凄,添加 MallocStackLoggingNoCompact,并且設(shè)置為YES)

    img

    這樣趾访,當(dāng)出現(xiàn)崩潰原因是message sent to deallocated instance 0x7179910态秧,我們可以使用以下命令,把內(nèi)存地址還原:(gdb) nfo malloc-history 0x7179910
    也可以使用下面的命令 (gdb) shell malloc_history {pid/partial-process-name} {address}

    這篇文章中有介紹MallocStackLoggingNoCompact的使用扼鞋。

  3. 總結(jié)

    還有官方文檔 Enabling the Malloc Debugging Features 介紹了類似NSZombieEnabledMallocStackLoggingNoCompact這類的環(huán)境變量的作用申鱼。

    TODO:翻譯Enabling the Malloc Debugging Features這篇文章,寫對應(yīng)的demo測試這類變量設(shè)置后如何找出內(nèi)存出錯(cuò)問題云头。

Enable Address Sanitizer(地址消毒劑)

img

設(shè)置這個(gè)參數(shù)后就能看到一些更詳細(xì)的錯(cuò)誤信息提示捐友,甚至?xí)袃?nèi)存使用情況的展示。

img

C語言是一門危險(xiǎn)的語言溃槐,內(nèi)存安全是一個(gè)主要的問題匣砖。C語言中根本沒有內(nèi)存安全可言。像下面的代碼昏滴,會被正常的編譯猴鲫,而且可能正常運(yùn)行: char *ptr = malloc(5); ptr[12] = 0; 對于內(nèi)存安全的驗(yàn)證已經(jīng)有一些解決方案了。如Clang的靜態(tài)代碼分析谣殊,可以從代碼中查找特定類型的內(nèi)存安全問題拂共。如Valgrind之類的程序可以在運(yùn)行時(shí)檢測到不安全的內(nèi)存訪問。

Address Sanitizer是另外一種解決方案姻几。它使用了一種新的方法宜狐,有利有弊势告。但仍不失為一個(gè)查找代碼問題的有力工具。

這類工具的理論依據(jù)是:訪問內(nèi)存時(shí)肌厨,通過比較訪問的內(nèi)存和程序?qū)嶋H分配的內(nèi)存培慌,驗(yàn)證內(nèi)存訪問的有效性豁陆,從而在bug發(fā)生時(shí)就檢測到它們柑爸,而不會等到副作用產(chǎn)生時(shí)才有所察覺。

malloc函數(shù)總是最少分配16個(gè)字節(jié)盒音。為了儲存針對標(biāo)準(zhǔn)malloc的內(nèi)存的保護(hù)表鳍,需要分配內(nèi)存到16字節(jié)的范圍內(nèi),因此祥诽,若分配的內(nèi)存大小不是16字節(jié)的整數(shù)倍譬圣,余出的幾個(gè)字節(jié)將不受保護(hù)。

Address Sanitizer會追蹤受限內(nèi)存雄坪,使用了一種簡單但是很巧妙的方法:它在進(jìn)程的內(nèi)存空間上保存了一個(gè)固定的區(qū)域厘熟,叫做“影子內(nèi)存區(qū)”。用內(nèi)存消毒劑的術(shù)語來說维哈,一個(gè)被標(biāo)記為受限的內(nèi)存被稱作“中毒”內(nèi)存绳姨。“影子內(nèi)存區(qū)”會記錄哪些內(nèi)存字節(jié)是中毒的阔挠。通過一個(gè)簡單的公式飘庄,可以將進(jìn)程中的內(nèi)存空間映射到“影子內(nèi)存區(qū)”中,即:每8字節(jié)的正常內(nèi)存塊映射到一個(gè)字節(jié)的影子內(nèi)存上购撼。在影子內(nèi)存上跪削,會跟蹤這8字節(jié)的“中毒狀態(tài)”。

Address Sanitizer 這篇文章詳細(xì)介紹了Enable Address Sanitizer迂求,對應(yīng)的中文翻譯在Xcode 7上直接使用Clang Address Sanitizer

Static Analyzer(靜態(tài)分析)

Static Analyzer是一個(gè)非常好的工具去發(fā)現(xiàn)編譯器警告不會提示的問題和一些個(gè)人的內(nèi)錯(cuò)泄露和死存儲(不會用到的賦了值的變量)錯(cuò)誤碾盐。這個(gè)方法可能大大的提高內(nèi)存使用和性能,以及提升應(yīng)用的整體穩(wěn)定性和代碼質(zhì)量揩局。

打開方式:Xcode->Product-Analyze 然后我們就能看到如下藍(lán)色箭頭所示的一些有問題的代碼毫玖。

img

unrecognized selector send to instancd 快速定位

在debug navigator的斷點(diǎn)欄里添加Create Symbolic Breakpoint。

img

在Symbolic中填寫如下方法簽名: -[NSObject(NSObject) doesNotRecognizeSelector:]

img

設(shè)置完成后再遇到類似的錯(cuò)誤就會定位到具體的代碼谐腰。

Signal和EXC_BAD_ACCESS錯(cuò)誤分析

Signal

在計(jì)算機(jī)科學(xué)中孕豹,信號(英語:Signals)是Unix、類Unix以及其他POSIX兼容的操作系統(tǒng)中進(jìn)程間通訊的一種有限制的方式十气。它是一種異步的通知機(jī)制励背,用來提醒進(jìn)程一個(gè)事件已經(jīng)發(fā)生。當(dāng)一個(gè)信號發(fā)送給一個(gè)進(jìn)程砸西,操作系統(tǒng)中斷了進(jìn)程正常的控制流程叶眉,此時(shí)址儒,任何非原子操作都將被中斷。如果進(jìn)程定義了信號的處理函數(shù)衅疙,那么它將被執(zhí)行莲趣,否則就執(zhí)行默認(rèn)的處理函數(shù)。

在iOS中就是未被捕獲的Objective-C異常(NSException)饱溢,導(dǎo)致程序向自身發(fā)送了SIGABRT信號而崩潰喧伞。

Signal信號的類型:

  • SIGABRT–程序中止命令中止信號
  • SIGALRM–程序超時(shí)信號
  • SIGFPE–程序浮點(diǎn)異常信號
  • SIGILL–程序非法指令信號
  • SIGHUP–程序終端中止信號
  • SIGINT–程序鍵盤中斷信號
  • SIGKILL–程序結(jié)束接收中止信號
  • SIGTERM–程序kill中止信號
  • SIGSTOP–程序鍵盤中止信號
  • SIGSEGV–程序無效內(nèi)存中止信號
  • SIGBUS–程序內(nèi)存字節(jié)未對齊中止信號
  • SIGPIPE–程序Socket發(fā)送失敗中止信號

iOS異常捕獲 這篇文章中有對各種信號的解釋。

  1. SIGSEGV

    SIGSEGV是當(dāng)SEGV發(fā)生的時(shí)候绩郎,讓代碼終止的標(biāo)識潘鲫。這是在iOS中最為常見導(dǎo)致崩潰的原因。當(dāng)App視圖去訪問沒有被開辟的內(nèi)存或者已經(jīng)被釋放的內(nèi)存時(shí)肋杖,這樣異常就會產(chǎn)生溉仑。

    調(diào)試這樣的Bug,需要找到已經(jīng)被釋放的變量,如果是在開發(fā)環(huán)境下發(fā)生這樣的問題状植,可以使用Zombies來檢測這樣的問題浊竟。

    在低內(nèi)存的情況下,也可能發(fā)生類似的問題津畸。這樣非常難重現(xiàn)振定,在類和方法中跟蹤堆棧可能會找到原因洼畅。

    舉個(gè)常見的例子吩案,在使用代理的時(shí)候:

       // 代理應(yīng)該是用weak修飾的
    self.delegate = myView;
    
    // myView從UINavigationController中Pop之后就會被銷毀,而self.delegate仍然起作用帝簇,成了野指針
    
    // 將會拋出異常
    [self.delegate doSomething];
    

    避免這種異撑枪可以在調(diào)用之前檢查一下代理是否為空,是否能夠響應(yīng)所給的Selector

    if(self.delegate != nil) {
        if([self.delegate respondsToSelector:@selector(doSomething)]) {
            [self.delegate doSomething];
        }
    }
    
  2. NSInvalidArgumentException

    通常根據(jù)堆棧信息能夠知道異常所發(fā)生的地方丧肴,最常見的引起NSInvalidArgumentException的原因就是an unrecognized selector残揉,當(dāng)調(diào)用一個(gè)對象不存在方法是就會拋出。

    比如:

    // 因?yàn)閷ο蟛荒茼憫?yīng)所調(diào)用的方法, 這就會拋出NSInvalidArgumentException異常
    [@”myString” objectForKey:@”someKey”];
    

    像上面這種明顯的錯(cuò)誤是編譯不過的芋浮,所以需要注意動(dòng)態(tài)參數(shù)抱环,比如序列化的JSON

    // data => [‘item0’, ‘item1’, ‘item2’]
    NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
    // JSON Data是個(gè)數(shù)組而不是一個(gè)字典
    
    // 錯(cuò)誤在數(shù)組中調(diào)用了字典的方法
    [json objectForKey:@”myKey”];
    

    解決這樣的問題纸巷,可以在獲取對象前镇草,判斷一下是否響應(yīng)這個(gè)放否,數(shù)組是否包含了某個(gè)元素瘤旨。

  3. SIGABRT

    這是一個(gè)讓程序終止的標(biāo)識梯啤,當(dāng)存在一個(gè)未處理的異常的時(shí)候debugger顯示。在一個(gè)已經(jīng)發(fā)布的app中存哲,SIGABRT會在斷言或者在app內(nèi)部因宇、操作系統(tǒng)滴啊用終止方法的時(shí)候拋出七婴。

    如果堆棧的信息是比較晦澀,看不懂察滑。很有可能是在蘋果SDK內(nèi)部終止了打厘。這樣的話,操作系統(tǒng)會殺死app并且拋出堆棧異常贺辰,這樣非常難調(diào)試户盯。

    注意:這并不一定意味著是系統(tǒng)的代碼存在Bug,代碼僅僅是成為了無效的狀態(tài),或者異常狀態(tài)魂爪。

    SIGABRT 通常發(fā)生在異步執(zhí)行系統(tǒng)方法的時(shí)候先舷,所以一定小心,比如 CoreData滓侍,Accessing files, NSUserDefaults,還有一些其他的系統(tǒng)多線程操作。

  4. SIGBUS

    這個(gè)標(biāo)識意味著 BUS Error牲芋,它和 SIGABRT 很容易混淆撩笆。兩者都有相同點(diǎn),都是在訪問不可用內(nèi)存的時(shí)候發(fā)生缸浦,SIGBUS 是在訪問的地址不存在夕冲,或者無效的隊(duì)列。換句話說裂逐,SIGBUS就是物理地址無效(不同于SIGSEGV歹鱼,SIGSEGV是邏輯地址無效)

    SIGBUS 可能從大多數(shù)的同步方法中發(fā)出,當(dāng)試圖訪問一個(gè)鎖的時(shí)候可能會拋出這樣的異常

  5. NSRangeException

    當(dāng)代碼中試圖去訪問對象的范圍內(nèi)不存在的索引的時(shí)候卜高,根據(jù)堆棧信息可以追溯到這個(gè)異常弥姻,比如:

    [__NSArrayM objectAtIndex:]: index 11 beyond bounds [0 .. 10]

    大多數(shù)情況下引起這個(gè)問題的原因是數(shù)組和字符串,比如:

    NSArray *arr = @[@1, @2, @3, @4];
    NSNumber *num = [arr objectAtIndex:9]; // 拋出異常
    
    NSString *mainString = @”myString”;
    NSString *subString = [mainString substringToIndex:24]; // 拋出異常
    

    避免這種異常很簡單掺涛,及時(shí)確保索引在對象的范圍內(nèi)庭敦,比如:

    NSNumber *obj = nil; 
    NSArray *arr = @[@1, @2, @3, @4];
    
    if([arr count] > 9) {
        obj = [arr objectAtIndex:9];
    }
    
    NSString *subString = nil; 
    NSString *mainString = @”myString”;
    
    if([mainString length] > 24) {
        subString = [mainString substringToIndex:24];
    }
    
  6. 截取Signal和Exception從容的崩潰

EXC_BAD_ACCESS

EXC_BAD_ACCESS是一個(gè)比較難處理的crash了,當(dāng)一個(gè)app進(jìn)入一種毀壞的狀態(tài)薪缆,通常是由于內(nèi)存管理問題而引起的時(shí)秧廉,就會出現(xiàn)出現(xiàn)這樣的crash。通常1.7.1中的Signal信號錯(cuò)誤都會提醒EXC_BAD_ACCESS拣帽。

文中1.3就介紹了用一些變量設(shè)置來找出這類錯(cuò)誤疼电。

崩潰類型收集

新老操作系統(tǒng)兼容

  • 原因

    開發(fā)人員在進(jìn)行開發(fā)的時(shí)候,常常使用的是某個(gè)操作系統(tǒng)版本减拭,所以在開發(fā)人員進(jìn)行開發(fā)測試的那個(gè)系統(tǒng)版本上基本不會出現(xiàn)問題蔽豺。但在其他版本上開發(fā)人員無法進(jìn)行完全的測試,這就導(dǎo)致了在新系統(tǒng)上運(yùn)行正常峡谊,但在舊系統(tǒng)上卻崩潰的情況茫虽。

    在新 iOS 上正常的應(yīng)用刊苍,到了老版本 iOS 上秒退最常見原因是系統(tǒng)動(dòng)態(tài)鏈接庫或Framework無法找到。這種情況通常是由于 App 引用了一個(gè)新版操作系統(tǒng)里的動(dòng)態(tài)庫(或者某動(dòng)態(tài)庫的新版本)或只有新 iOS 支持的 Framework濒析,而又沒有對老系統(tǒng)進(jìn)行測試正什,于是當(dāng) App 運(yùn)行在老系統(tǒng)上時(shí)便由于找不到而秒退。

    還有就是有些方法是新版操作系統(tǒng)才支持的号杏,而又沒有對該方法是否存在于老系統(tǒng)中做出判斷婴氮。這種情況其實(shí)還是比較難出現(xiàn)的,除非開發(fā)人員太low了盾致,因?yàn)檫@類方法在xcode編碼時(shí)編輯器都會有提醒的主经。

  • 解決

    這種問題一般就是用戶升級操作系統(tǒng)或者開發(fā)人員修改問題以兼容老系統(tǒng)。

本地存儲的數(shù)據(jù)結(jié)構(gòu)改變

  • 原因

    程序在升級時(shí)庭惜,修改了本地存儲的數(shù)據(jù)結(jié)構(gòu)罩驻,但是對用戶既存的舊數(shù)據(jù)沒有做好升級,結(jié)果導(dǎo)致初始化時(shí)因?yàn)闊o法正確讀取用戶數(shù)據(jù)而秒退护赊。

  • 解決

    第一種: 是把服務(wù)端傳過來的一些信息保存在本地惠遏,使用的時(shí)候從本地?cái)?shù)據(jù)庫取。

    剛開始的時(shí)候我是第一次從服務(wù)端得到數(shù)據(jù)的時(shí)候直接解析成對應(yīng)的model然后存入plist文件里面骏啰。這時(shí)就有一個(gè)問題节吮,比如服務(wù)端新傳了字段newId,但是我舊版model里面沒有定義過判耕,存入本地的數(shù)據(jù)還是沒有這個(gè)字段透绩。

    然后等我升級了程序,新程序里model壁熄,定義了這個(gè)newId字段帚豪,但是舊版里面數(shù)據(jù)已經(jīng)保存過一遍了沒有這個(gè)字段。這時(shí)再去取就取不到了请毛。

    所以后來我就把存儲時(shí)解析數(shù)據(jù)改成了讀取時(shí)解析數(shù)據(jù)志鞍。就是不管服務(wù)端傳什么數(shù)據(jù)都把它存下來,然后在使用的時(shí)候再把它解析成對應(yīng)的model方仿,這樣就不會丟失字段了固棚。

    第二種: 自己的一些數(shù)據(jù)存儲在本地SQLlite,新版的時(shí)候表結(jié)構(gòu)改了仙蚜。

    SQLlite只支持更改一個(gè)表的名字此洲,或者向表中增加一個(gè)字段(列),但是我們不能刪除一個(gè)已經(jīng)存在的字段委粉,或者更改一個(gè)已經(jīng)存在的字段的名稱呜师、數(shù)據(jù)類型、限定符等等贾节。

    這種就是有時(shí)候新版又添加字段了汁汗,或者改變了字段的名稱了衷畦。一般來說原有的字段名稱不應(yīng)該改變,但是添加新字段是常有的事知牌。

    一般做法是在第一次創(chuàng)建表的時(shí)候加一些冗余字段祈争,以防后面不時(shí)之需。但是如果真沒辦法需要在舊表上增加新字段了角寸,那就要做數(shù)據(jù)遷移了菩混。

    這里有一個(gè)庫在FMDB基礎(chǔ)上管理SQLlite數(shù)據(jù)庫了,可以用來做數(shù)據(jù)遷移用扁藕。FMDBMigrationManager

訪問的數(shù)據(jù)為空或訪問數(shù)據(jù)類型不對

  • 原因

    這類情況是比較常見的沮峡,后端傳回了空數(shù)據(jù),客戶端沒有做對應(yīng)的判斷繼續(xù)執(zhí)行下去了亿柑,這樣就產(chǎn)生了crash邢疙。或者自己本地的某個(gè)數(shù)據(jù)為空數(shù)據(jù)而去使用了橄杨。還有就是訪問的數(shù)據(jù)類型不是期望的數(shù)據(jù)類型而產(chǎn)生崩潰秘症。

  • 解決

    第一種: 服務(wù)端都加入默認(rèn)值,不返回空內(nèi)容或無key式矫,但是服務(wù)端往往會不太愿意改,還有就是有些確實(shí)應(yīng)該無值的話key也不用傳役耕,減少數(shù)據(jù)量的傳輸采转。

    第二種: 這種就是客戶端自己做判斷,如果每次都是自己去if判斷是否為空或格式是否正確那肯定是比較麻煩的瞬痘。所以這里用到了NSArray和NSDictionary的Category故慈。一般我們訪問的數(shù)據(jù)都是NSArray或NSDictionary,所以在取值方法里面做一下判斷框全,返回正確的數(shù)據(jù)類型或默認(rèn)值即可察绷。

操作了不該操作的對象,野指針之類

野指針介紹

iOS中有空指針和野指針兩種概念津辩。

空指針是沒有存儲任何內(nèi)存地址的指針拆撼。如Student *s1 = NULL;Student *s2 = nil;

而野指針是指指向一個(gè)已刪除的對象(”垃圾”內(nèi)存既不可用內(nèi)存)或未申請?jiān)L問受限內(nèi)存區(qū)域的指針。野指針是比較危險(xiǎn)的喘沿。因?yàn)橐爸羔樦赶虻膶ο笠呀?jīng)被釋放了闸度,不能用了,你再給被釋放的對象發(fā)送消息就是違法的蚜印,所以會崩潰莺禁。

空指針和野指針這篇文章介紹了空指針和野指針的概念。

野指針崩潰情況

野指針訪問已經(jīng)釋放的對象crash其實(shí)不是必現(xiàn)的窄赋,因?yàn)閐ealloc執(zhí)行后只是告訴系統(tǒng)哟冬,這片內(nèi)存我不用了楼熄,而系統(tǒng)并沒有就讓這片內(nèi)存不能訪問。

所以野指針的崩潰是比較隨機(jī)的浩峡,你在測試的時(shí)候可能沒發(fā)生crash可岂,但是用戶在使用的時(shí)候就可能發(fā)生crash了。

現(xiàn)實(shí)出現(xiàn)問題大概是下面幾種可能的情況:

  1. 對象釋放后內(nèi)存沒被改動(dòng)過红符,原來的內(nèi)存保存完好青柄,可能不Crash或者出現(xiàn)邏輯錯(cuò)誤(隨機(jī)Crash)。
  2. 對象釋放后內(nèi)存沒被改動(dòng)過预侯,但是它自己析構(gòu)的時(shí)候已經(jīng)刪掉某些必要的東西致开,可能不Crash、Crash在訪問依賴的對象比如類成員上萎馅、出現(xiàn)邏輯錯(cuò)誤(隨機(jī)Crash)双戳。
  3. 對象釋放后內(nèi)存被改動(dòng)過,寫上了不可訪問的數(shù)據(jù)糜芳,直接就出錯(cuò)了很可能Crash在objc_msgSend上面(必現(xiàn)Crash飒货,常見)。
  4. 對象釋放后內(nèi)存被改動(dòng)過峭竣,寫上了可以訪問的數(shù)據(jù)塘辅,可能不Crash、出現(xiàn)邏輯錯(cuò)誤皆撩、間接訪問到不可訪問的數(shù)據(jù)(隨機(jī)Crash)扣墩。
  5. 對象釋放后內(nèi)存被改動(dòng)過,寫上了可以訪問的數(shù)據(jù)扛吞,但是再次訪問的時(shí)候執(zhí)行的代碼把別的數(shù)據(jù)寫壞了呻惕,遇到這種Crash只能哭了(隨機(jī)Crash,難度大滥比,概率低)Q谴唷!
  6. 對象釋放后再次release(幾乎是必現(xiàn)Crash盲泛,但也有例外濒持,很常見)。

參考下面這張圖:

img

內(nèi)存處理不當(dāng)

說到因?yàn)閮?nèi)存處理不當(dāng)崩潰就要涉及到內(nèi)存管理問題了查乒。內(nèi)存管理是軟件開發(fā)中一個(gè)重要的課題弥喉。iOS自從引入ARC機(jī)制后,對于內(nèi)存的管理開發(fā)者好像輕松了很多玛迄,但是還會發(fā)生一些內(nèi)存泄露之類的問題由境。

那我們?nèi)绾慰焖俚膩碚页鰞?nèi)存泄露呢?以前我們可能會使用Instruments來監(jiān)測,但是我們會發(fā)現(xiàn)使用Instruments特別繁瑣虏杰,而且不一定能定位到內(nèi)存泄露讥蟆。

所以這里偉大的Facebook工程師們開源了一些自動(dòng)化工具來解決監(jiān)測內(nèi)存泄露問題:FBRetainCycleDetectorFBAllocationTracker纺阔、FBMemoryProfiler瘸彤。
原文介紹:Automatic memory leak detection on iOS
中文翻譯:在iOS上自動(dòng)檢測內(nèi)存泄露

主線程UI長時(shí)間卡死,被系統(tǒng)殺掉

主線程被卡住是非常常見的場景笛钝,具體表現(xiàn)就是程序不響應(yīng)任何的UI交互质况。這時(shí)按下調(diào)試的暫停按鈕,查看堆棧玻靡,就可以看到是到底是死鎖结榄、死循環(huán)等,導(dǎo)致UI線程被卡住囤捻。

這部分需要研究多線程臼朗,還有如何看調(diào)試欄里的線程的信息。

iOS調(diào)試?yán)锏倪M(jìn)程跟線程 這篇文章中有介紹多線程及死鎖的原因蝎土。

多線程之間切換訪問引起的crash

多線程引起的崩潰大部分是因?yàn)槭褂脭?shù)據(jù)庫的時(shí)候多線程同時(shí)讀寫數(shù)據(jù)庫而造成了crash视哑。 多線程導(dǎo)致的iOS閃退分析這篇文章就是關(guān)于多線程crash的調(diào)試。

多個(gè) Crash 日志收集服務(wù)共存的坑

在自己的程序里集成多個(gè) Crash 日志收集服務(wù)實(shí)在不是明智之舉誊涯。通常情況下挡毅,第三方功能性 SDK 都會集成一個(gè) Crash 收集服務(wù),以及時(shí)發(fā)現(xiàn)自己 SDK 的問題暴构。當(dāng)各家的服務(wù)都以保證自己的 Crash 統(tǒng)計(jì)正確完整為目的時(shí)慷嗜,難免出現(xiàn)時(shí)序手腳,強(qiáng)行覆蓋等等的惡意競爭丹壕,總會有人默默被坑。

  • 如果同時(shí)有多方通過 NSSetUncaughtExceptionHandler 注冊異常處理程序薇溃,和平的作法是:

    后注冊者通過 NSGetUncaughtExceptionHandler 將先前別人注冊的 handler 取出并備份菌赖,在自己 handler 處理完后自覺把別人的 handler 注冊回去,規(guī)規(guī)矩矩的傳遞沐序。不傳遞強(qiáng)行覆蓋的后果是琉用,在其之前注冊過的日志收集服務(wù)寫出的 Crash 日志就會因?yàn)槿〔坏?NSException 而丟失Last Exception Backtrace等信息。(P.S. iOS 系統(tǒng)自帶的 Crash Reporter 不受影響)

  • 在開發(fā)測試階段策幼,可以利用 fishhook 框架去 hookNSSetUncaughtExceptionHandler方法邑时,這樣就可以清晰的看到 handler 的傳遞流程斷在哪里,快速定位污染環(huán)境者特姐。不推薦利用調(diào)試器添加符號斷點(diǎn)來檢查晶丘,原因是一些 Crash 收集框架在調(diào)試狀態(tài)下是不工作的。

  • 檢測事例代碼:

    static NSUncaughtExceptionHandler *g_vaildUncaughtExceptionHandler;
    static void (*ori_NSSetUncaughtExceptionHandler)( NSUncaughtExceptionHandler * );
    void my_NSSetUncaughtExceptionHandler( NSUncaughtExceptionHandler * handler)
    {
        g_vaildUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
        if (g_vaildUncaughtExceptionHandler != NULL) {
            NSLog(@"UncaughtExceptionHandler=%p",g_vaildUncaughtExceptionHandler);
        }
    
        ori_NSSetUncaughtExceptionHandler(handler);
        NSLog(@"%@",[NSThread callStackSymbols]);
    
        g_vaildUncaughtExceptionHandler = NSGetUncaughtExceptionHandler();
        NSLog(@"UncaughtExceptionHandler=%p",g_vaildUncaughtExceptionHandler);
    }
    

文章寫得比較用心轉(zhuǎn)至: iOS 崩潰Crash解析

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市浅浮,隨后出現(xiàn)的幾起案子沫浆,更是在濱河造成了極大的恐慌,老刑警劉巖滚秩,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件专执,死亡現(xiàn)場離奇詭異,居然都是意外死亡郁油,警方通過查閱死者的電腦和手機(jī)本股,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來桐腌,“玉大人拄显,你說我怎么就攤上這事×ú簦” “怎么了凿叠?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長嚼吞。 經(jīng)常有香客問我盒件,道長,這世上最難降的妖魔是什么舱禽? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任炒刁,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己随抠,他們只是感情好蓉驹,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著竖共,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脖镀,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音狼电,去河邊找鬼蜒灰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛肩碟,可吹牛的內(nèi)容都是我干的强窖。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼削祈,長吁一口氣:“原來是場噩夢啊……” “哼翅溺!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤未巫,失蹤者是張志新(化名)和其女友劉穎窿撬,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體叙凡,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡劈伴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了握爷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片跛璧。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖新啼,靈堂內(nèi)的尸體忽然破棺而出追城,到底是詐尸還是另有隱情,我是刑警寧澤燥撞,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布座柱,位于F島的核電站,受9級特大地震影響物舒,放射性物質(zhì)發(fā)生泄漏色洞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一冠胯、第九天 我趴在偏房一處隱蔽的房頂上張望火诸。 院中可真熱鬧,春花似錦荠察、人聲如沸置蜀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盯荤。三九已至,卻和暖如春焕盟,著一層夾襖步出監(jiān)牢的瞬間廷雅,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工京髓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人商架。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓堰怨,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蛇摸。 傳聞我的和親對象是個(gè)殘疾皇子备图,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,320評論 8 265
  • 前言 iOS崩潰是讓iOS開發(fā)人員比較頭痛的事情,app崩潰了,說明代碼寫的有問題揽涮,這時(shí)如何快速定位到崩潰的地方很...
    齊滇大圣閱讀 65,233評論 29 443
  • 關(guān)于Mongodb的全面總結(jié) MongoDB的內(nèi)部構(gòu)造《MongoDB The Definitive Guide》...
    中v中閱讀 31,898評論 2 89
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,089評論 1 32
  • 151頁錢錢把自己的解釋又再總結(jié)了一遍抠藕,說:“當(dāng)你朝著積極的目標(biāo)去思考的時(shí)候,就不會心生畏懼蒋困《芩疲” 182頁“有一個(gè)...
    行走的螞蟻yqb閱讀 207評論 0 0