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]	// 崩溃
复制代码

服务器端出现问题而形成的崩溃

有一种状况,就是服务器端传递数据给客户端,客户端将其解析成模型对象,而后取模型里的值插入字面量语法构造的数组或者字典中。若是服务器端发生了问题,而客户端没有保护措施就会受到连累,固然实际上服务器端百分之九十九的几率是不可能发生问题的,因此不少人(包括我)也理所固然不会去特地多写一些多余的防护性代码。 我上面只是举了一个例子,通常咱们都会和服务器端约定好数据格式以及其余细节,并且大多数时候都会作一些保护,但我真正想强调的是客户端不崩溃必定优于客户端依赖于服务器端而不崩溃,尽量避免受到外界的影响数组

其余崩溃

  1. 在iOS 9.0以后NSNotificationCenter不会对一个dealloc的观察者发送消息,因此若是应用最低版本是9.0,其实也没必要每次都去移除通知,但若是须要支持更低的版本,仍是必定要移除通知,不然会崩溃
  2. KVO不移除监听会致使奔溃,因此KVO的添加和移除必须成双成对
  3. 内存泄露,最著名的是代理和被代理对象循环引用

总结

以上都是我曾经遇到过的崩溃状况,固然还有不少我不知道的状况,毕竟技术是复杂的。咱们或许可使用一些工具来检查或者避免崩溃,但我仍是想强调平时对待代码要更加严谨,对待负责的项目要更有责任感bash

上述代码的CrashDemos连接:github.com/iroyzhang/i…服务器

相关文章
相关标签/搜索