当前位置: 首页 > news >正文

iOS开发的底线-崩溃

前言

作为一个刚毕业从事iOS开发不久的人,最初同事以及导师都叮嘱我写代码的时候一定要注意异常情况,底线就是不能写出任何有可能造成崩溃的代码。实际上,项目中有监测崩溃的工具,而且review的时候也会很严格检查,所以基本上那种有可能造成崩溃的代码基本都会在上线前修正。

但就在前些天,客户端发生了大面积一打开就闪退的问题,影响非常严重,后来查出是引入的其他部门的SDK没有进行类型判断而导致的崩溃。或许是开发人员的不小心,但我觉得更多的是平时没有养成习惯,没有考虑到对于一个拥有千万级别用户的应用来说,即使是万分之一的崩溃概率,也会有数千个用户崩溃,这在竞争激烈的互联网市场,是不能被容忍的。

我平时也没有过度重视,因为我总觉得理论上应该不可能崩溃,但是实际的场景太多,理论上不可能并不是百分百不可能,作为足够严谨的开发人员,必须守住自己的底线,不只是知道什么情况会造成崩溃,而是要养成一种编程习惯,所以特意分析了各种崩溃的情况。

数组越界

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的添加和移除必须成双成对
  • 内存泄露,最著名的是代理和被代理对象循环引用

总结

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

上述代码的CrashDemos链接:github.com/iroyzhang/i…

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

转载于:https://juejin.im/post/5cefd26051882556174dd13d

相关文章:

  • LEANGOO卡片
  • margin-top 外边距合并
  • Visual Paradigm 教程[UML]:如何在序列图中应用消息编号?
  • 【漫画】为什么说O(n)复杂度的基数排序没有快速排序快?
  • 博客园的第一篇随笔
  • vue之修饰符
  • 赶紧收藏!41个Web UI工具包资源免费及付费下载
  • 共勉:作为一名程序员你应该怎么提一个高质量的问题?
  • 反转单链表和双链表
  • OSPF 笔记之Master / Slave
  • Codeforces Round #564 (Div. 2) A. Nauuo and Votes
  • iOS 开发调试工具
  • linux5.7 启动命令总结
  • T-MBA学习营 | 寒窗十数载,我们原来并不会学习?
  • 自定义博客总结
  • JavaScript 如何正确处理 Unicode 编码问题!
  • @angular/forms 源码解析之双向绑定
  • 【comparator, comparable】小总结
  • 2018以太坊智能合约编程语言solidity的最佳IDEs
  •  D - 粉碎叛乱F - 其他起义
  • go语言学习初探(一)
  • JavaScript标准库系列——Math对象和Date对象(二)
  • Joomla 2.x, 3.x useful code cheatsheet
  • Js实现点击查看全文(类似今日头条、知乎日报效果)
  • OSS Web直传 (文件图片)
  • React+TypeScript入门
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • Swoft 源码剖析 - 代码自动更新机制
  • Terraform入门 - 1. 安装Terraform
  • vue总结
  • 阿里云爬虫风险管理产品商业化,为云端流量保驾护航
  • 关于for循环的简单归纳
  • 利用阿里云 OSS 搭建私有 Docker 仓库
  • 如何用vue打造一个移动端音乐播放器
  • 山寨一个 Promise
  • 详解移动APP与web APP的区别
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • ​Python 3 新特性:类型注解
  • %check_box% in rails :coditions={:has_many , :through}
  • (PHP)设置修改 Apache 文件根目录 (Document Root)(转帖)
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (五)大数据实战——使用模板虚拟机实现hadoop集群虚拟机克隆及网络相关配置
  • (五)网络优化与超参数选择--九五小庞
  • (一)ClickHouse 中的 `MaterializedMySQL` 数据库引擎的使用方法、设置、特性和限制。
  • (一)Mocha源码阅读: 项目结构及命令行启动
  • (一)为什么要选择C++
  • (已解决)什么是vue导航守卫
  • (转)linux 命令大全
  • (转)用.Net的File控件上传文件的解决方案
  • .NET Core跨平台微服务学习资源
  • .NET Standard 支持的 .NET Framework 和 .NET Core
  • .NET 简介:跨平台、开源、高性能的开发平台
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)...
  • .net6 webapi log4net完整配置使用流程
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比