iOS開發的底線-崩潰

前言

做爲一個剛畢業從事iOS開發不久的人,最初同事以及導師都叮囑我寫代碼的時候必定要注意異常狀況,底線就是不能寫出任何有可能形成崩潰的代碼。實際上,項目中有監測崩潰的工具,並且review的時候也會很嚴格檢查,因此基本上那種有可能形成崩潰的代碼基本都會在上線前修正。git

但就在前些天,客戶端發生了大面積一打開就閃退的問題,影響很是嚴重,後來查出是引入的其餘部門的SDK沒有進行類型判斷而致使的崩潰。或許是開發人員的不當心,但我以爲更多的是平時沒有養成習慣,沒有考慮到對於一個擁有千萬級別用戶的應用來講,即便是萬分之一的崩潰機率,也會有數千個用戶崩潰,這在競爭激烈的互聯網市場,是不能被容忍的。github

我平時也沒有過分重視,由於我總以爲理論上應該不可能崩潰,可是實際的場景太多,理論上不可能並非百分百不可能,做爲足夠嚴謹的開發人員,必須守住本身的底線,不僅是知道什麼狀況會形成崩潰,而是要養成一種編程習慣,因此特地分析了各類崩潰的狀況。編程

數組越界

NSArray *firstNames = @[@"Roy", @"Mike", @"Jordan"];
NSString *name = firstNames[3];	// 崩潰

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

分析:
能夠看出當前數組的範圍是0..2,當前下標超出了範圍,即訪問了未知的內存空間

注:
除了數組可能越界以外,字符串也有可能越界,例如執行substringWithRange:消息時若是傳遞了過大的範圍也會崩潰
複製代碼

字面量數組和字典插入nil值

數組
NSString *name;
NSArray *firstNames = @[@"Roy", @"Mike", @"Jordan", name];	//崩潰

崩潰信息:
**** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSPlaceholderArray initWithObjects:count:]: attempt to insert nil object from objects[3]'*
*****

分析:
經過崩潰信息能夠很清楚看到是由於在字典初始化的時候插入了nil,實際上字面量語法是一種語法糖,本質是先建立了一個數組,而後把方括號內的全部對象添加到這個數組中

注:
字面量語法讓代碼更加簡潔,也能及時發現錯誤,可是最後建立的數組是不可變的
複製代碼
字典
NSNumber *jordanAge;
NSDictionary *ages = @{@"Roy":@22, @"Mike":@24, @"Jordan":jordanAge};		//崩潰

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

分析:
同上面緣由同樣,都是插入了nil而致使的崩潰

注:
當key爲nil的時候插入也會崩潰
複製代碼

Unrecognized Selector

id person = @"person";
[person objectForKey:@"name"];	//崩潰

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

分析:
person對象沒法執行objectForKey:消息,因此最後崩潰了

注:
在用Objective-C語言編碼時,咱們會經常使用id類型更加便利地聲明變量,但在執行消息前必定要肯定它是否能響應,可以使用respondsToSelector:檢查。最多見的場景是調用代理方法,即便指定了代理對象,也不必定保證代理實現了相應方法(協議裏還有可選實現的方法)
複製代碼

NaN崩潰

float number = NAN;
NSDictionary *dict = @{@"value" : @(number)};
NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingSortedKeys error:nil];

崩潰信息:
**** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Invalid number value (NaN) in JSON write'*
*****

分析:
能夠先來判斷dict對象是否能被轉換成JSON數據:
BOOL isValidJSONObject = [NSJSONSerialization isValidJSONObject:dict];
isValidJSONObject的結果是NO,也就是dict對象沒法被轉換爲JSON數據,即NaN類型不能被用於JSON對象中

注:
當進行不正常的數學運算時不僅是會產生NaN類型,也有可能產生+inf類型,雖然並不會直接形成崩潰,但有可能在用它們進行其餘操做的時候會有可能形成崩潰。經過isnan(x)和isinf(x)方法能夠判斷nan和inf類型
複製代碼

富文本初始化時字符串爲空

NSString *text;
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text]; // 崩潰

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

分析:
從崩潰信息中能夠很明顯看到是由於傳入的變量值爲nil而崩潰

注:
構造NSMutableString時,若是傳入的字符串爲nil也會崩潰
複製代碼

建立未註冊過的UITableViewCell

UITableViewCell *cell = [tableView    
dequeueReusableCellWithIdentifier:@"reuseIdentifier" 
forIndexPath:indexPath]	// 崩潰
複製代碼

服務器端出現問題而形成的崩潰

有一種狀況,就是服務器端傳遞數據給客戶端,客戶端將其解析成模型對象,而後取模型裏的值插入字面量語法構造的數組或者字典中。若是服務器端發生了問題,而客戶端沒有保護措施就會受到連累,固然實際上服務器端百分之九十九的機率是不可能發生問題的,因此不少人(包括我)也理所固然不會去特地多寫一些多餘的防護性代碼。 我上面只是舉了一個例子,通常咱們都會和服務器端約定好數據格式以及其餘細節,並且大多數時候都會作一些保護,但我真正想強調的是客戶端不崩潰必定優於客戶端依賴於服務器端而不崩潰,儘量避免受到外界的影響數組

其餘崩潰

  • 在iOS 9.0以後NSNotificationCenter不會對一個dealloc的觀察者發送消息,因此若是應用最低版本是9.0,其實也沒必要每次都去移除通知,但若是須要支持更低的版本,仍是必定要移除通知,不然會崩潰
  • KVO不移除監聽會致使奔潰,因此KVO的添加和移除必須成雙成對
  • 內存泄露,最著名的是代理和被代理對象循環引用

總結

以上都是我曾經遇到過的崩潰狀況,固然還有不少我不知道的狀況,畢竟技術是複雜的。咱們或許可使用一些工具來檢查或者避免崩潰,但我仍是想強調平時對待代碼要更加嚴謹,對待負責的項目要更有責任感bash

上述代碼的CrashDemos連接:github.com/iroyzhang/i…服務器

(原文地址:juejin.im/post/5c3b01…app

相關文章
相關標籤/搜索