iOS客户端如何将APP崩溃率降低到万分之一以下

当然崩溃率和日活是有关系的,我只能说我的APP肯定不是只有几万日活的APP。程序的稳定性不用我多说,其重要性是不言而喻的。如果APP动不动就崩溃,那就不用说什么交互什么用户体验了,用户的第一反应就是直接把APP删掉或者找替代你的APP。

如何降低崩溃率呢,先分一下一下崩溃的原因:
一、内存管理问题;
二、容错处理不完善;
三、webview与其他崩溃。

首先内存问题,我们不得不回顾以下历史,在很久很久以前的蛮荒时代,这个时代里,手机内存最小的只有128Mb,一个APP可用的内存更少,还要手动管理内存,程序猿们苦不堪言。
这个时代程序猿的内功心法是:谁创建,谁是释放;需要时申请,不需要时释放。但是实际写代码时,我们小心翼翼,也很难避免不出现问题。典型问题如
应该释放的内存忘了释放导致内存驻留;不该释放的你释放了,直接crash;内存已经释放了,指针不去置空,出现野指针。

庆幸的是13年以后,随着设备可用内存的增加,苹果强制使用自动内存管理(ARC)了。这个时候我们很不情愿的使用了ARC,虽然大家还在抨击苹果这个ARC真垃圾,清理内存不及时,不能有效控制内存的峰值,不如我们自己管理内存。但是我们要说这个ARC的确减轻了我们的负担,让我们编程更加的高效。在这个是时代里,原则就是只要有强指针指向这块内存,这块内存就会驻留,不会被释放;一旦没有强指针指向这块内存,这块内存就在系统方便的时候被回收了。这样程序猿就不用关心引用计数,不用release对象,野指针消失不见,崩溃明显减少。
ARC解决了大部分问题,但是我们要记住两点,一是使用cf对象的时候要记得自己创建的对象,自己记得release;而是避免循环引用。我遇到的问题是团队对于这种循环引用认识不足,因为即使有这种循环引用,APP照常运行,感觉不到什么问题,问题是感觉不到问题才是问题。我会问从内存管理方面APP为何会崩溃,回答是内存过大。其实问题在于内存峰值过高,系统出于保护自己的目的,shut了我们的APP。而导致内存峰值过高的罪魁祸首很大一部分来自于我们的内存泄漏。不断的内存泄漏,使得我们的APP占用内存越来越大,同时系统有不能及时清除,到达一定程度,APP运行开始缓慢甚至崩溃就不可避免了。delegate循环引用问题不大,基本上是block循环引用造成的问题。其实典型问题

__block TestModel*tModel = self.testModel;
 self.testModel.bClick = ^{ 
              [tModel.array addObject:@"1"];
              [self pushNext];
  };```
这段实例代码大家很熟悉,如果只能发现一处循环引用,就需要注意了。一方面self持有testModel对象,testModel持有bClick的block,block又调用self,持有了self导致循环引用。另一方面testModel不需要用block来修饰,同时testModel对象的block持有了testModel自身,造成循环引用。更多block内存问题,请自行谷哥。

再者就是容错处理问题,这个问题从两方面来考虑:

一方面,我们需要增强我们代码的健壮性,该容错的地方进行必要的容错处理,举个例子,我们常见的崩溃引发问题,如数组越界,setObject:forKey:空值,initWithString:空值,数据类型不匹配。如果不进行有效的容错判断,裸奔的效果就像内存泄漏一样,测试没问题,到了线上崩溃就出现了,特别是APP的体积越来越大,后台逻辑越来越复杂,用户越来越多的情况下。比如字符串,建议进行如下判断:

if (string && [string isKindOfClass:[NSString class]] && string.length > 0) {
}```
当然我使用了类别来拓展常用类型的判断使用时比较方便。类似这样:

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"]){
}

另一方面,我们如果处处都加这种判断,固然是好,但是总有漏网之鱼。另外我们不知道什么时候服务器就把字典传成了数组,不做处理的话就坑了,服务器出问题了我们客户端跟着崩,还说我们的代码不健壮。这个之后就需要利用运行时动态的替换方法来规避这种问题。例如setObject:forKey:问题,我们load的时候,进行方法替换:

+ (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];
}```

进一步引申,这个时候在崩溃的地方,我们是可以获取到堆栈信息的,我们可以把这些存起来,自建崩溃监控系统,来发现APP隐藏的crash。另外要注意的是这样hook目前只能拦截特定方法还不能拦截类型不匹配造成的unrecognized selector send to instance问题。

最后是webview与其他崩溃。webview占很大内存特别是UIWebView,所以单独和大家说一下,一定不要在webview的vc里面出现循环引用,这样可能会导致大量内存无法释放。另外,webview记得在dealloc中将delegate置为nil,同时删除缓存数据,减少其所占的内存。webview内的h5页面如果本身不注意内存管理或者一些bug也会造成崩溃,只能让前段多注意了。有时我们在观测崩溃的时候,发现一些webcore的崩溃,崩溃率出现峰值后来又趋于正常,很可能就是h5页面上线了一些bug页面后来修复了。大家可以设置接收APP崩溃的邮件,及时反馈,及时解决。(崩溃监控建议大家使用bugly,自动上传dysm,堆栈信息都解析出来了,不用自己手动解析堆栈信息)。

我们还遇到一些常见的崩溃,主要是大家的代码习惯问题了,如观察者或者通知中心忘移除、观察者移除崩溃、多次push同一个控制器、NSTimer等。这些问题大家自己多注意就OK了。最后一定要注意多线程读取数据的问题以及避免非主线程操作UI。

面对这些问题,我们好好做了,崩溃率自然会下降,但是依赖自查还是不能完全避免问题。这就要每次提测前用工具走一遍,查看是否还遗留有问题。我的习惯:

1.Analyze进行静态检查
2.Instruments的leaks进行动态分析
3.Instruments的Allocations分析一下是否有大内存占用
4.MLeakFinder查一下循环引用

最后希望大家还是要注意培养自己良好的代码习惯,清晰的数据结构,提高代码质量。

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

References
[MLeakFinder](https://github.com/Zepo/MLeaksFinder)
[h5Crash研究](http://web.jobbole.com/86309/)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容