前言
此文是基于這些年工作中項目里面常見崩潰的一些總結,整理出來方便查閱,希望對大家都有所幫助。
App常見崩潰
- 數(shù)組下標越界
- 字典構造與修改
-
NSAttributedString
相關 - 呈現(xiàn)一個空控制器
- 強引用一個單例對象
- unrecognized selector
- 操作
tableView
數(shù)據(jù) - Push到同一個控制器多次
1.數(shù)組下標越界
示例代碼:
- (void)testArrayOutOfBounds
{
NSArray *testArray = @[@1,@2,@3];
NSNumber *num = testArray[3];
}
異常現(xiàn)象:
Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 3 beyond bounds [0 .. 2]'
預防方案:
在數(shù)組中取值時需要先進組下標索引邊界檢查,如果沒有越界方可取值。
2.字典構造造與修改
示例代碼:
- (void)testDicSetNilValueCrash
{
// 構造不可變字典時 key和value都不能為空
NSString *nilValue = nil;
NSString *nilKey = nil;
NSDictionary *dic1 = @{@"key" : nilValue};
NSDictionary *dic2 = @{nilKey : @"value"};
}
異常現(xiàn)象:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[0]'
預防方案:
在我們使用字面量快速創(chuàng)建一個字典的時候需要特別小心,因為很可能字典的鍵和值不能保證同時不為空。有潛在崩潰的風險,這種崩潰非常容易出現(xiàn),需要特別小心,但是當你留心的話也非常好避免,就是設置字典的鍵或者值的時候判斷是否非空,可變字典設置某個鍵的值是可以為空,相當于刪除字典中的某個鍵值。為了使App保持健壯推薦使用KVO
的方式來設置字典的值
- (void)testMutableDicSetNilValueCrash
{
NSString *value = nil;
NSMutableDictionary *mDic = [NSMutableDictionary dictionary];
// via Dic set, leading crash
[mDic setObject:value forKey:@"key"];
// via KVO set, it's safe
[mDic setValue:value forKey:@"key"];
}
3.NSAttributedString相關
示例代碼:
- (void)testAttributedStringInitCrash
{
NSString *nilStr = nil;
NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:nilStr];
}
- (void)testAttributedStringAddAttributeCrash
{
NSString *nonnullStr = @"str";
NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:nonnullStr];
NSString *nilValue = nil;
[attributedStr addAttribute:NSAttachmentAttributeName value:nilValue range:NSMakeRange(0, 1)];
}
異常現(xiàn)象:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSConcreteMutableAttributedString initWithString:: nil value'
預防方案:
在構造NSMutableAttributedString
或者NSAttributedString
需要留意,設置的屬性的值是否有可能存在nil
的情況。這個很容易被人忽視,值得注意。
4.強引用一個單例對象
異常現(xiàn)象:
App隨時有可能面臨崩潰,這個在曾經(jīng)的一次網(wǎng)絡請求封裝的過程中遇到過,NSURLSession
,不要強引用該對象,否則當你釋放引用它的對象然后創(chuàng)建新的對象引用它很可能導致App崩潰。
預防方案:
對單例的Property
不要使用strong
,非要引用的話使用week
。
5.unrecognized selector
示例代碼:
- (void)testUnrecogernizedSelectorCash
{
[self performSelector:@selector(testSel) withObject:nil afterDelay:0];
}
異常現(xiàn)象:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController testSel]: unrecognized selector sent to instance 0x7ffd41609d10'
預防方案:
此類崩潰經(jīng)常出現(xiàn),特別是當服務器數(shù)據(jù)放回異常時,比如本來應該返回一個NSString
類型字符串,結果返回NULL
,當你調(diào)用字符串的length
方式時,導致App崩潰。預防方法,重要的地方對類型進行判斷再調(diào)用該類的相關方法,或者寫一個分類統(tǒng)一處理此類邏輯。
6.操作tableView數(shù)據(jù)
示例代碼:
- (void)testTableViewUpdateCrash
{
NSIndexPath *insertIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForRow:1 inSection:0];
NSIndexPath *reloadIndexPath = [NSIndexPath indexPathForRow:2 inSection:0];
NSIndexPath *moved1IndexPath = [NSIndexPath indexPathForRow:3 inSection:0];
NSIndexPath *moved2IndexPath = [NSIndexPath indexPathForRow:4 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];
}
異常現(xiàn)象:
Fatal Exception: NSInternalInconsistencyException
Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (1 inserted, 0 deleted).
預防方案:
當需要動態(tài)更新tableView
的數(shù)據(jù)時,計算好模型的數(shù)據(jù)使模型的數(shù)據(jù)和更新tableView
的后的數(shù)據(jù)保持同步。
7.Push到同一個控制器多次
異常現(xiàn)象:
Fatal Exception: NSInvalidArgumentException
Pushing the same view controller instance more than once is not supported (<PPSelectPayMethodViewControllerIOS7: 0x10d7e7f10>)
參考鏈接:
以上就是工作中常見的異常崩潰以及處理方案,下面的異常分類內(nèi)容來自Apple
的官方文檔,有興趣的可以查閱。??
Apple官方常見異常類型(Exception types)
- 訪問一塊壞內(nèi)存
[EXC_BAD_ACCESS] // SIGSEGV // SIGBUS]
- 異常退出
[EXC_CRASH // SIGABRT]
- 追蹤受限
[EXC_BREAKPOINT // SIGTRAP]
- 非法指令
[EXC_BAD_INSTRUCTION // SIGILL]
- 被保護的資源遭到侵害
[EXC_GUARD]
- 資源限制
[EXC_RESOURCE]
- 其他異常類型
1.訪問一塊壞內(nèi)存(Bad Memory Access)
當程序試圖接入無效內(nèi)容或者嘗試以不被允許的方式接入由于內(nèi)存的保護等級(例如,嘗試寫入只讀的內(nèi)存)。Exception Subtype
字段包含一個kern_return_t結構體用來描述錯誤和不正確接入的內(nèi)存地址。
下面是一些調(diào)試壞內(nèi)存接入導致崩潰的建議:
- 假如objc_msgSend或者objc_release在崩潰線程回溯(Backtraces)的頂部附近,這個線程可能嘗試給一個釋放的對象發(fā)消息。你應該profile應用使用Zombies instrument來更好的理解這個崩潰發(fā)生的條件。
- 假如gpus_ReturnNotPermittedKillClient在崩潰線程回溯(Backtraces)的頂部附近,線程被終結因為它嘗試用OpenGL ES或者Metal執(zhí)行渲染當程序處于后臺時。查看QA1766: How to fix OpenGL ES application crashes when moving to the background
- 打開
Address Sanitizer
運行你的應用。Address Sanitizer添加了額外的說明在內(nèi)容接入當你編譯代碼的時候。隨著你應用的運行,Xcode將??你假如內(nèi)存以一種可能導致崩潰的方式接入。
2.異常退出(Abnormal Exit)
程序異常退出,是最常見導致這類異常崩潰的原因是捕獲到Objective-C/C++
異常和調(diào)用了abort()
函數(shù)。
App Extensions將被終結發(fā)生這種類型的異常,假如他們初始化花費太多的時間(watchdog終結)。假如一個extension由于載入時間太長被終結,產(chǎn)生崩潰報告的Exception Subtype將是LAUNCH_HANG。因為extensions并沒有一個main
函數(shù),任何花銷在初始化的時間都發(fā)生在static constructors和呈現(xiàn)在你的extensions和依賴庫的+load
方法。你應該盡可能的延遲做這些工作。
3.追蹤受限(Trace Trap)
和異常退出類似,這種異常的目的是給一個追加的調(diào)試器,讓它有機會來打斷在一個當它執(zhí)行時候指定的點的進程。你可以使用__builtin_trap()
函數(shù)在你的代碼中來觸發(fā)這個異常。假如沒有調(diào)試器追加的話,線程將被終結并且產(chǎn)生一個崩潰報告。
低等級的庫(例如libdispatch)將受限這個進程一旦遇到一個重大的錯誤。關于錯誤的額外信息可以在Additional Diagnostic Information章節(jié)中的崩潰報告找到,或者在設備的控制臺。
假如在runtime
遇到諸如下面的一個意外的條件,Swift
代碼將終結出現(xiàn)這種類型的異常:
- 非可選類型帶有一個
nil
值 - 錯誤的強制類型轉換
查看Backtraces來決發(fā)生定異常條件的位置。額外的信息可能已經(jīng)在設備的控制臺打印出來了。你應該修改崩潰處的代碼來優(yōu)雅的處理runtime
錯誤。例如,使用Optional Binding而不是強制解包一個可選變量。
4.非法指令(Illegal Instruction)
進程嘗試執(zhí)行一個非法或者未定義的指令。進程可能已經(jīng)嘗試跳進到一個無效的地址通過一個配置錯誤的函數(shù)指針。在Intel
處理器中,ud2
操作碼導致一個EXC_BAD_INSTRUCTION異常,但是它通常被用來困住進程達到調(diào)試的目的。Swift
代碼在Intel
處理器中將以這種異常終結,假如在runtime
位置條件發(fā)生。更多詳情查看Trace Trap。
5.被保護的資源遭到侵害(Guarded Resource Violation)
進程侵犯一個被保護的資源。系統(tǒng)庫可能某個文件的描述器成guarded
,在那以后,所有不正常的操作在這些描述器上都將觸發(fā)一個EXC_GUARD異常(當它想操作在這些文件描述器上,系統(tǒng)可以使用特殊的guarded
標記的私有APIs)。這可以幫你向下快速追蹤問題,例如關閉一個已經(jīng)被系庫打開的文件描述器。例如,假如一個app關閉文件秒殺器通過使用截圖SQLite
文件到一個Core Data
存儲,Core Data
將會在隨后詭異的崩潰。guard exception將讓這些問題盡早引起你的注意,這樣也讓他們變得更容易調(diào)試。
崩潰報告來自新版的iOS
包含了人類可讀的詳細信息關于引起EXC_GUARD
異常的操作在Exception Subtype
和Exception Message
字段中。在來自macOS
或者老版本的iOS
的崩潰報告中,這些信息被編碼到第一個Exception Code
就像一個分解成如下的位段:
-
[63:61] - Guard Type:被保護的資源類型。
0x2
代表一個文件描述器資源。 -
[60:32] - Flavor:侵害被處罰時的條件
- 假如第一個
(1 << 0)
位被設置,進程嘗試執(zhí)行close()
函數(shù)在一個受保護的文件描述器。 - 假如第二個
(1 << 1)
位被設置,進程嘗試執(zhí)行dup()
,dup2()
,或者fcntl()
帶F_DUPFD
或者F_DUPFD_CLOEXEC
命令在一個受保護的文件描述器。 - 假如第三個
(1 << 2)
位被設置,進程嘗試通過一個socket
發(fā)送給一個受保護的文件描述器。 - 假如第三個
(1 << 3)
位被設置,進程嘗試寫入到一個受保護的文件描述器。
- 假如第一個
- [31:0] - File Descriptor:進程嘗試修改的受保護的文件描述器。
6.資源限制(Resource Limit)
進程超出了一個資源消耗的限制。這是一個來自操作系統(tǒng)通知,告訴進程正在使用的資源過多。精確的資源列在Exception Subtype
字段中。假如Exception Note
字段包含NON-FATAL CONDITION
,進程不會被終結即使產(chǎn)生了一個崩潰報告。
異常子類型
MEMORY
表明進程已經(jīng)越過系統(tǒng)應用的內(nèi)存限制。這可能是一個終結的先兆由于超額的使用內(nèi)存。-
異常子類型
WAKEUPS
表明在進程中的線程每秒被喚醒太多次,這強制CPU
非常頻繁的喚醒消耗電池壽命。典型的,這個通過由線程與線程的通信產(chǎn)生(通常是使用
peformSelector:onThread:
或dispatch_async
),那樣無意的發(fā)生了遠遠超出它正常應該的切換頻率。因為通信的協(xié)調(diào)發(fā)生得非常頻繁而出發(fā)此類的異常,這個通常和多個后臺線程有著相似Backtraces
-- 表明那些地方發(fā)生過通信。
7.其他異常類型(Other Exception Types)
一些崩潰報告可能含有一個未命名的Exception Type
,將以一個16進制的值(例如,00000020)的形式打印。假如你的設備收到了一個這樣的崩潰報告,直接查看Exception Codes
字段尋找更多的信息。
異常代碼
0xbaaaaaad
表明記錄是整個系統(tǒng)的stackshot
,不是一個崩潰報告。為了獲得一個stackshot
,按Home
鍵和任意音量鍵。這些記錄經(jīng)常被用戶偶然創(chuàng)建,并不表明是一個錯誤。異常代碼
0xbad22222
表明一個VoIP
應用已經(jīng)被iOS
終結,因為它啟動得太頻繁。異常代碼
0x8badf00d
表明應用已經(jīng)被iOS
終結因為發(fā)生watchdog
超時。應用花費太長時間啟動,終結,或者響應系統(tǒng)事件。通常導致這歌問題是做了在主線程執(zhí)行了同步的網(wǎng)絡請求。無論什么操作在Thread 0
都需要移動到后臺線程,或者異步處理,以免它阻塞主線程。異常代碼
0xc00010ff
表明引用被操作系統(tǒng)終結為了響應一個發(fā)熱事件。這個可能由于一個發(fā)生崩潰的特定的設備的問題或者環(huán)境被操作導致。為了使你的應用更高效運行的建議,查看WWDC session iOS Performance and Power Optimization with Instruments。異常代碼
0xdead10cc
表明應用被iOS
終結,由于當在后臺運行時它持有了一個系統(tǒng)的資源(像通信錄數(shù)據(jù)庫)。-
異常代碼
0xdeadfa11
表明應用被用戶強制退出。強制退出發(fā)生在當用戶第一次按下開關機按鈕直到"滑動來關機"出現(xiàn),然后在按下Home鍵。這是合理的假如用戶這樣做了,因為應用已經(jīng)變得不可響應,但是這并不能保證 - 強制退出任何正在運行的任務。注意:終結一個掛起的app通過從多任務關系面板中移除并不會產(chǎn)生一個崩潰報告。一旦一個app被掛起,iOS它有資格在任何時候終結它,所有沒有崩潰報告產(chǎn)生。