關于iOS崩潰的認知和一些避免

一個APP的崩潰率是對一個前端人員是否為合格的一個審核標準.
那么在每天日常中開發(fā)迭代中的那么多崩潰率信息有哪些呢?
又是因為什么原因?qū)е碌哪?該怎么去避免?

APP常見崩潰:

  1. Container crash(數(shù)組越界,插nil等)
  2. 字典的構(gòu)造與修改
  3. 操作 UITableView UICollectionView 數(shù)據(jù)增刪改讀操作
  4. NSString crash (字符串操作的crash)
  5. unrecognized selector
  6. UI not on Main Thread Crash (非主線程刷UI (機制待改善))

1.數(shù)組下標的越界

簡易示例代碼:

- (void)testArrayOut {
    NSArray *array = @[@"a", @"b", @"c"];
    NSLog(@"%@", array[4]);
}

崩潰信息
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndexedSubscript:]: index 4 beyond bounds [0 .. 2]'

一般對數(shù)組的操作出現(xiàn)Crash的情況:

* 取值:index超出array的索引范圍
* 添加:新增的object為nil或者Null
* 插入:index大于count济赎、插入的object為nil或者Null
* 刪除:index超出array的索引范圍
* 替換:index超出array的索引范圍酬蹋、替換的object為nil或者Null

2.字典的構(gòu)造與修改

簡易示例代碼:

- (void)testDictionCrash {
    NSString *aKey = nil;
    NSDictionary *dictionary = @{@"a": aKey};
    NSLog(@"%@", dictionary);
}

一般對字典操作出現(xiàn)Crash的情況:

  • NSDictionary 不支持 nil 作為 key.
  • NSDictionary 不支持 nil 作為 value.

崩潰信息

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]'

以上預防方法:
    1. 替換系統(tǒng)方法.通過 Method Swizzling 替換成自己的方法,然后再執(zhí)行方法的時候加以判斷.
    1. 使用類別擴展一個方法自定義實現(xiàn)對應操作后的預防.
    1. 萬能基佬王:http://t.cn/R0CTokN
    1. 萬能谷歌法:http://t.cn/R0CTNZE

3.操作 UITableView UICollectionView 數(shù)據(jù)增刪改讀操作

簡易示例代碼:

- (void)testTableViewUpdateCrash {
    NSIndexPath *insertIndexPath = [NSIndexPath indexPathForRow:10 inSection:0];
    NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForRow:11 inSection:0];
    NSIndexPath *reloadIndexPath = [NSIndexPath indexPathForRow:12 inSection:0];
    NSIndexPath *moved1IndexPath = [NSIndexPath indexPathForRow:13 inSection:0];
    NSIndexPath *moved2IndexPath = [NSIndexPath indexPathForRow:14 inSection:0];
    [self.tableView beginUpdates];
    [self.tableView insertRowsAtIndexPaths:@[insertIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView deleteRowsAtIndexPaths:@[deleteIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView reloadRowsAtIndexPaths:@[reloadIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView moveRowAtIndexPath:moved1IndexPath toIndexPath:moved2IndexPath];
    [self.tableView endUpdates];
}

崩潰信息

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'attempt to delete row 12 from section 0 which only contains 10 rows before the update'

一般出現(xiàn)Crash情況:

  • 1.返回的數(shù)據(jù)源 和 insert / delete 后,section所包含的行數(shù)不一致.
  • 2.刷新對應行數(shù)的UI沒有對應上數(shù)據(jù)源.
預防方法:
  • 1.當需要動態(tài)更新tableView的數(shù)據(jù)時惋嚎,計算好模型的數(shù)據(jù)使模型的數(shù)據(jù)和更新tableView的后的數(shù)據(jù)保持同步。
  • 2.在調(diào)用deleteRowsAtIndexPaths:方法前,要確保數(shù)據(jù)為最新希痴。也就是說,先將要刪除的數(shù)據(jù)從數(shù)據(jù)源中刪除春感。
  • 3.分組和分組中行數(shù)是變動的砌创,不能寫死.
  • 4.先操作數(shù)據(jù)源,再操作UITableView對應的刷新動畫

4.NSAttributedString,NSMutableString 等可變對象操作相關

簡易示例代碼

- (void)testMutableAttributedStringCrash {
    NSString *string = nil;
    NSMutableString *mutableString  = [[NSMutableString alloc] initWithString:string];
    NSLog(@"%@",mutableString);
    NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:string];
    NSLog(@"%@",attributedString);
}

崩潰信息
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSConcreteMutableAttributedString initWithString:: nil value'

一般出現(xiàn)Crash情況:

1.初始化一個空的String.
2.拼接,插入,替換等操作一個nil的值.

預防方法:

    1. 檢查參數(shù)和參數(shù)類型的判斷.

5.unrecognized selector

簡易示例代碼:

- (void)testUnrecognizedSelectorCash {
    [self performSelector:@selector(testSelCrash) withObject:nil afterDelay:0];
}

崩潰信息
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController testSelCrash]: unrecognized selector sent to instance 0x7f93a1e03e80'

一般出現(xiàn)Crash的情況:

    1. 對象被提前釋放,指針變成野指針.
    1. 對象本身就是野指針.如聲明一個局部對象鲫懒,沒有初始化就直接調(diào)用.
    1. 一個正常的對象調(diào)用一個不存在的方法.
    1. 給實體對象發(fā)送了不認識的消息嫩实,即對象調(diào)用方法出錯.
預防方法:

runtime中具體的方法調(diào)用流程大致如下:

1.首先,在相應操作的對象中的緩存方法列表中找調(diào)用的方法窥岩,如果找到甲献,轉(zhuǎn)向相應實現(xiàn)并執(zhí)行。
2.如果沒找到颂翼,在相應操作的對象中的方法列表中找調(diào)用的方法晃洒,如果找到,轉(zhuǎn)向相應實現(xiàn)執(zhí)行
3.如果沒找到朦乏,去父類指針所指向的對象中執(zhí)行1球及,2步驟.
4.以此類推,如果一直到根類還沒找到呻疹,轉(zhuǎn)向攔截調(diào)用吃引,走消息轉(zhuǎn)發(fā)機制。
5.如果沒有重寫攔截調(diào)用的方法,程序報錯际歼。

由上圖可見惶翻,在一個函數(shù)找不到時,runtime提供了三種方式去補救:
1鹅心、調(diào)用resolveInstanceMethod給機會讓類添加這個實現(xiàn)這個函數(shù)
2吕粗、調(diào)用forwardingTargetForSelector讓別的對象去執(zhí)行這個函數(shù)
3、調(diào)用forwardInvocation(函數(shù)執(zhí)行器)靈活的將目標函數(shù)以其他形式執(zhí)行旭愧。
如果都不中颅筋,調(diào)用doesNotRecognizeSelector拋出異常。

通過實現(xiàn)NSObject的forwardingTargetForSelector:方法.并利用class_addMethod方法動態(tài)添加函數(shù).

選擇原因:
1.resolveInstanceMethod 需要在類的本身上動態(tài)添加它本身不存在的方法输枯,這些方法對于該類本身來說冗余的

  1. forwardInvocation可以通過NSInvocation的形式將消息轉(zhuǎn)發(fā)給多個對象议泵,但是其開銷較大,需要創(chuàng)建新的NSInvocation對象桃熄,并且forwardInvocation的函數(shù)經(jīng)常被使用者調(diào)用先口,來做多層消息轉(zhuǎn)發(fā)選擇機制,不適合多次重寫
  2. forwardingTargetForSelector可以將消息轉(zhuǎn)發(fā)給一個對象瞳收,開銷較小碉京,并且被重寫的概率較低,適合重寫
可參考優(yōu)秀文獻:

6.非主線程刷UI

在非主線程刷UI將會導致app運行crash,現(xiàn)在Xcode在編譯的時候會檢測出當前有子線程操作UI有對應====的提示.但是其實也有必要對其進行處理螟深。

預防方案處理

  • 詳細見Demo

以上為比較常見的簡單崩潰和一些簡單的預防方法,還有一些難以重現(xiàn)的崩潰.如野指針.內(nèi)存泄露導致的崩潰等問題.

APP常見不好跟蹤的異常崩潰:

  1. SIGSEGV || EXC_BAD_ACCESS || SIGBUS 野指針
  2. SIGPIPE 異常
  3. EXC_CRASH || SIGABRT 異常退出
  4. 內(nèi)存泄露

1.EXC_BAD_ACCESS || SIGSEGV || SIGBUS 野指針

出現(xiàn)原因:

試圖訪問未分配給自己的內(nèi)存,或試圖往沒有寫權(quán)限的內(nèi)存地址寫數(shù)據(jù).另外,在低內(nèi)存的時候,也可能會產(chǎn)生這樣的異常.


預防方法:

  • 提高野指針Crash率.參考文獻:

如何定位Obj-C野指針隨機Crash(一):http://t.cn/R0NZLTU
如何定位Obj-C野指針隨機Crash(二):http://t.cn/R0NZbXM
如何定位Obj-C野指針隨機Crash(三):http://t.cn/R0NZqWM

2.SIGPIPE 異常

出現(xiàn)原因:

對一個端已經(jīng)關閉的socket調(diào)用兩次寫入操作谐宙,第二次寫入將會產(chǎn)生SIGPIPE信號,該信號默認結(jié)束進程界弧。

預防方法:

// 僅在 IOS 系統(tǒng)上支持 SO_NOSIGPIPE
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)
    // We do not want SIGPIPE if writing to socket.
    const int value = 1;
    setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(int));
#endif

將改代碼放在PCH文件中即可.

可參考文獻:

1.http://t.cn/R0Cs9T0 (維基百科)
2.http://t.cn/R0CselE
3.http://t.cn/R0NvcIx

3.EXC_CRASH || SIGABRT 異常退出

出現(xiàn)原因:

程序異常退出,導致這類異常崩潰的原因是捕獲到Objective-C/C++異常和調(diào)用了abort()函數(shù),會在斷言/app內(nèi)部/操作系統(tǒng)用終止方法拋出.通常發(fā)生在異步執(zhí)行系統(tǒng)方法的時候,如CoreData/NSUserDefaults等,還有一些其他的系統(tǒng)多線程操作.這并不一定意味著是系統(tǒng)代碼存在bug,代碼僅僅是成了無效狀態(tài),或者異常狀態(tài).

預防方法:

如果他們需要太多的時間來初始化凡蜻,程序?qū)⒈唤K止,因為觸發(fā)了看門狗垢箕。如果是因為啟動的時候被掛起划栓,所產(chǎn)生的崩潰報告異常類型(Exception Subtype)將是launch_hang。因為擴展(extensions)并沒有一個main函數(shù)条获,任何花銷在初始化的時間都發(fā)生在靜態(tài)構(gòu)造函數(shù)(static constructors)和呈現(xiàn)在你的擴展(extensions)和依賴庫的+load方法茅姜。你應該盡可能的延遲做這些工作。

4.內(nèi)存泄露

出現(xiàn)原因:

程序運行時一直分配內(nèi)存而不及時釋放無用的內(nèi)存月匣,程序占用的內(nèi)存越來越大钻洒,直到把系統(tǒng)分配給該APP的內(nèi)存消耗殫盡,程序因無內(nèi)存可用導致崩潰锄开,這樣的情況我們稱之為內(nèi)存泄漏素标。

預防方法:

1.使用Xcode自帶的 Instruments 內(nèi)存分析工具(Leaks)
2.使用WeRead團隊提供的自動化工具來監(jiān)測內(nèi)存泄露問題

pod 'MLeaksFinder' 
pod 'FBRetainCycleDetector

原理:為基類 NSObject 添加一個方法 -willDealloc 方法,該方法的作用是萍悴,先用一個弱指針指向 self头遭,并在一小段時間(3秒)后寓免,通過這個弱指針直接調(diào)用斷言.
可參考文獻:
MLeaksFinder:精準 iOS 內(nèi)存泄露檢測工具: http://t.cn/RGC7Gdg

結(jié)束語

該文的介紹不包含所有崩潰,只是拋磚引玉與大家一起討論學習討論.
為了少讓我們少受Bug的摧殘,還提心吊膽的擔心線上某個崩潰問題引起大的影響计维。希望我們輕松工作袜香,快樂生活,早日擺脫bug煩惱鲫惶。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜈首,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子欠母,更是在濱河造成了極大的恐慌欢策,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赏淌,死亡現(xiàn)場離奇詭異踩寇,居然都是意外死亡,警方通過查閱死者的電腦和手機六水,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門俺孙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人掷贾,你說我怎么就攤上這事鼠冕。” “怎么了胯盯?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長计露。 經(jīng)常有香客問我博脑,道長,這世上最難降的妖魔是什么票罐? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任叉趣,我火速辦了婚禮,結(jié)果婚禮上该押,老公的妹妹穿的比我還像新娘疗杉。我一直安慰自己,他們只是感情好蚕礼,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布烟具。 她就那樣靜靜地躺著,像睡著了一般奠蹬。 火紅的嫁衣襯著肌膚如雪朝聋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天囤躁,我揣著相機與錄音冀痕,去河邊找鬼荔睹。 笑死,一個胖子當著我的面吹牛言蛇,可吹牛的內(nèi)容都是我干的僻他。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼腊尚,長吁一口氣:“原來是場噩夢啊……” “哼吨拗!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起跟伏,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤丢胚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后受扳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體携龟,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年勘高,在試婚紗的時候發(fā)現(xiàn)自己被綠了峡蟋。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡华望,死狀恐怖蕊蝗,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赖舟,我是刑警寧澤蓬戚,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站宾抓,受9級特大地震影響子漩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜石洗,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一幢泼、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧讲衫,春花似錦缕棵、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至枷畏,卻和暖如春忽匈,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背矿辽。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工丹允, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留郭厌,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓雕蔽,卻偏偏與公主長得像折柠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子批狐,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345