iOS客戶端如何將APP崩潰率降低到萬分之一以下

當(dāng)然崩潰率和日活是有關(guān)系的,我只能說我的APP肯定不是只有幾萬日活的APP。程序的穩(wěn)定性不用我多說,其重要性是不言而喻的。如果APP動不動就崩潰,那就不用說什么交互什么用戶體驗了,用戶的第一反應(yīng)就是直接把APP刪掉或者找替代你的APP。

如何降低崩潰率呢,先分一下一下崩潰的原因:
一、內(nèi)存管理問題;
二、容錯處理不完善;
三、webview與其他崩潰。

首先內(nèi)存問題,我們不得不回顧以下歷史,在很久很久以前的蠻荒時代,這個時代里,手機(jī)內(nèi)存最小的只有128Mb,一個APP可用的內(nèi)存更少,還要手動管理內(nèi)存,程序猿們苦不堪言。
這個時代程序猿的內(nèi)功心法是:誰創(chuàng)建,誰是釋放;需要時申請,不需要時釋放。但是實際寫代碼時,我們小心翼翼,也很難避免不出現(xiàn)問題。典型問題如
應(yīng)該釋放的內(nèi)存忘了釋放導(dǎo)致內(nèi)存駐留;不該釋放的你釋放了,直接crash;內(nèi)存已經(jīng)釋放了,指針不去置空,出現(xiàn)野指針。

慶幸的是13年以后,隨著設(shè)備可用內(nèi)存的增加,蘋果強(qiáng)制使用自動內(nèi)存管理(ARC)了。這個時候我們很不情愿的使用了ARC,雖然大家還在抨擊蘋果這個ARC真垃圾,清理內(nèi)存不及時,不能有效控制內(nèi)存的峰值,不如我們自己管理內(nèi)存。但是我們要說這個ARC的確減輕了我們的負(fù)擔(dān),讓我們編程更加的高效。在這個是時代里,原則就是只要有強(qiáng)指針指向這塊內(nèi)存,這塊內(nèi)存就會駐留,不會被釋放;一旦沒有強(qiáng)指針指向這塊內(nèi)存,這塊內(nèi)存就在系統(tǒng)方便的時候被回收了。這樣程序猿就不用關(guān)心引用計數(shù),不用release對象,野指針消失不見,崩潰明顯減少。
ARC解決了大部分問題,但是我們要記住兩點,一是使用cf對象的時候要記得自己創(chuàng)建的對象,自己記得release;而是避免循環(huán)引用。我遇到的問題是團(tuán)隊對于這種循環(huán)引用認(rèn)識不足,因為即使有這種循環(huán)引用,APP照常運行,感覺不到什么問題,問題是感覺不到問題才是問題。我會問從內(nèi)存管理方面APP為何會崩潰,回答是內(nèi)存過大。其實問題在于內(nèi)存峰值過高,系統(tǒng)出于保護(hù)自己的目的,shut了我們的APP。而導(dǎo)致內(nèi)存峰值過高的罪魁禍?zhǔn)缀艽笠徊糠謥碜杂谖覀兊膬?nèi)存泄漏。不斷的內(nèi)存泄漏,使得我們的APP占用內(nèi)存越來越大,同時系統(tǒng)有不能及時清除,到達(dá)一定程度,APP運行開始緩慢甚至崩潰就不可避免了。delegate循環(huán)引用問題不大,基本上是block循環(huán)引用造成的問題。其實典型問題

__block TestModel*tModel = self.testModel;
 self.testModel.bClick = ^{ 
              [tModel.array addObject:@"1"];
              [self pushNext];
  };```
這段實例代碼大家很熟悉,如果只能發(fā)現(xiàn)一處循環(huán)引用,就需要注意了。一方面self持有testModel對象,testModel持有bClick的block,block又調(diào)用self,持有了self導(dǎo)致循環(huán)引用。另一方面testModel不需要用block來修飾,同時testModel對象的block持有了testModel自身,造成循環(huán)引用。更多block內(nèi)存問題,請自行谷哥。

再者就是容錯處理問題,這個問題從兩方面來考慮:

一方面,我們需要增強(qiáng)我們代碼的健壯性,該容錯的地方進(jìn)行必要的容錯處理,舉個例子,我們常見的崩潰引發(fā)問題,如數(shù)組越界,setObject:forKey:空值,initWithString:空值,數(shù)據(jù)類型不匹配。如果不進(jìn)行有效的容錯判斷,裸奔的效果就像內(nèi)存泄漏一樣,測試沒問題,到了線上崩潰就出現(xiàn)了,特別是APP的體積越來越大,后臺邏輯越來越復(fù)雜,用戶越來越多的情況下。比如字符串,建議進(jìn)行如下判斷:

if (string && [string isKindOfClass:[NSString class]] && string.length > 0) {
}```
當(dāng)然我使用了類別來拓展常用類型的判斷使用時比較方便。類似這樣:

NSStirng+Extension
+ (BOOL)isValid:(NSString *)string{
  if (string && [string isKindOfClass:[NSString class]] && string.length > 0) {
      return YES;
  }else{
      return NO;
  }
}
使用時
if ([NSString isValid:@"good boy"]){
}

另一方面,我們?nèi)绻幪幎技舆@種判斷,固然是好,但是總有漏網(wǎng)之魚。另外我們不知道什么時候服務(wù)器就把字典傳成了數(shù)組,不做處理的話就坑了,服務(wù)器出問題了我們客戶端跟著崩,還說我們的代碼不健壯。這個之后就需要利用運行時動態(tài)的替換方法來規(guī)避這種問題。例如setObject:forKey:問題,我們load的時候,進(jìn)行方法替換:

+ (void)load{
          Class dictCls = NSClassFromString(@"__NSDictionaryM");
        Method originalMethod = class_getInstanceMethod(dictCls,      @selector(setObject:forKey:));
Method swizzledMethod = class_getInstanceMethod(dictCls, @selector(lcx_setObject:forKey:));
        if (!originalMethod || !swizzledMethod) {
            return;}
  method_exchangeImplementations(originalMethod, swizzledMethod);
}

- (void)lcx_setObject:(id)anObject forKey:(id)aKey{
      if (anObject == nil) {
        NSLog(@"crash---NSMutableDictionary set nil object");
        return;
      }
      if (aKey == nil) {
          NSLog(@"crash---NSMutableDictionary set nil key");
          return;
        }
      [self lcx_setObject:anObject forKey:aKey];
}```

進(jìn)一步引申,這個時候在崩潰的地方,我們是可以獲取到堆棧信息的,我們可以把這些存起來,自建崩潰監(jiān)控系統(tǒng),來發(fā)現(xiàn)APP隱藏的crash。另外要注意的是這樣hook目前只能攔截特定方法還不能攔截類型不匹配造成的unrecognized selector send to instance問題。

最后是webview與其他崩潰。webview占很大內(nèi)存特別是UIWebView,所以單獨和大家說一下,一定不要在webview的vc里面出現(xiàn)循環(huán)引用,這樣可能會導(dǎo)致大量內(nèi)存無法釋放。另外,webview記得在dealloc中將delegate置為nil,同時刪除緩存數(shù)據(jù),減少其所占的內(nèi)存。webview內(nèi)的h5頁面如果本身不注意內(nèi)存管理或者一些bug也會造成崩潰,只能讓前段多注意了。有時我們在觀測崩潰的時候,發(fā)現(xiàn)一些webcore的崩潰,崩潰率出現(xiàn)峰值后來又趨于正常,很可能就是h5頁面上線了一些bug頁面后來修復(fù)了。大家可以設(shè)置接收APP崩潰的郵件,及時反饋,及時解決。(崩潰監(jiān)控建議大家使用bugly,自動上傳dysm,堆棧信息都解析出來了,不用自己手動解析堆棧信息)。

我們還遇到一些常見的崩潰,主要是大家的代碼習(xí)慣問題了,如觀察者或者通知中心忘移除、觀察者移除崩潰、多次push同一個控制器、NSTimer等。這些問題大家自己多注意就OK了。最后一定要注意多線程讀取數(shù)據(jù)的問題以及避免非主線程操作UI。

面對這些問題,我們好好做了,崩潰率自然會下降,但是依賴自查還是不能完全避免問題。這就要每次提測前用工具走一遍,查看是否還遺留有問題。我的習(xí)慣:

1.Analyze進(jìn)行靜態(tài)檢查
2.Instruments的leaks進(jìn)行動態(tài)分析
3.Instruments的Allocations分析一下是否有大內(nèi)存占用
4.MLeakFinder查一下循環(huán)引用

最后希望大家還是要注意培養(yǎng)自己良好的代碼習(xí)慣,清晰的數(shù)據(jù)結(jié)構(gòu),提高代碼質(zhì)量。

DEMO請移步:https://github.com/dudongdaoqi/LCXCrashExtension

References
[MLeakFinder](https://github.com/Zepo/MLeaksFinder)
[h5Crash研究](http://web.jobbole.com/86309/)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • //聯(lián)系人:石虎QQ: 1224614774昵稱:嗡嘛呢叭咪哄 1.如何追蹤app崩潰率,如何解決線上閃退 當(dāng)iO...
    石虎132閱讀 9,048評論 3 23
  • 內(nèi)存管理 簡述OC中內(nèi)存管理機(jī)制。與retain配對使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 2,080評論 1 16
  • 前言 現(xiàn)在iOS開發(fā)已經(jīng)是arc甚至是swift的時代,但是內(nèi)存管理仍是一個重點關(guān)注的問題,如果只知盲目開發(fā)而不知...
    明仔Su閱讀 26,789評論 16 175
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,716評論 25 709
  • (繼續(xù)不斷完善中) 一個小學(xué)(2次被拒),中學(xué)(3次被拒),大學(xué)(考3年),工作(肯德基招聘唯一落選)和...
    船長戚戈XGeek001閱讀 299評論 0 0

友情鏈接更多精彩內(nèi)容