App常见崩溃问题分析

前言

此文是基于这些年工作中项目里面常见崩溃的一些总结,整理出来方便查阅,希望对大家都有所帮助。

App常见崩溃

  1. 数组下标越界
  • 字典构造与修改
  • NSAttributedString相关
  • 呈现一个空控制器
  • 强引用一个单例对象
  • unrecognized selector
  • 操作tableView数据
  • Push到同一个控制器多次

1.数组下标越界

示例代码

- (void)testArrayOutOfBounds
{
    NSArray *testArray = @[@1,@2,@3];
    
    NSNumber *num = testArray[3];
}

异常现象

Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 3 beyond bounds [0 .. 2]'

预防方案

在数组中取值时需要先进组下标索引边界检查,如果没有越界方可取值。

2.字典构造造与修改

示例代码

- (void)testDicSetNilValueCrash
{
    // 构造不可变字典时 key和value都不能为空
    NSString *nilValue = nil;
    NSString *nilKey = nil;
    NSDictionary *dic1 = @{@"key" : nilValue};
    NSDictionary *dic2 = @{nilKey : @"value"};
}

异常现象

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

预防方案

在我们使用字面量快速创建一个字典的时候需要特别小心,因为很可能字典的键和值不能保证同时不为空。有潜在崩溃的风险,这种崩溃非常容易出现,需要特别小心,但是当你留心的话也非常好避免,就是设置字典的键或者值的时候判断是否非空,可变字典设置某个键的值是可以为空,相当于删除字典中的某个键值。为了使App保持健壮推荐使用KVO的方式来设置字典的值

- (void)testMutableDicSetNilValueCrash
{
    NSString *value = nil;
    NSMutableDictionary *mDic = [NSMutableDictionary dictionary];
    
    // via Dic set, leading crash
    [mDic setObject:value forKey:@"key"];
    
    // via KVO set, it's safe
    [mDic setValue:value forKey:@"key"];
}

3.NSAttributedString相关

示例代码

- (void)testAttributedStringInitCrash
{
    NSString *nilStr = nil;
    NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:nilStr];
}


- (void)testAttributedStringAddAttributeCrash
{
    NSString *nonnullStr = @"str";
    NSMutableAttributedString *attributedStr = [[NSMutableAttributedString alloc] initWithString:nonnullStr];
    
    NSString *nilValue = nil;
    [attributedStr addAttribute:NSAttachmentAttributeName value:nilValue range:NSMakeRange(0, 1)];
}

异常现象

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSConcreteMutableAttributedString initWithString:: nil value'

预防方案

在构造NSMutableAttributedString或者NSAttributedString需要留意,设置的属性的值是否有可能存在nil的情况。这个很容易被人忽视,值得注意。

4.强引用一个单例对象

异常现象

App随时有可能面临崩溃,这个在曾经的一次网络请求封装的过程中遇到过,NSURLSession,不要强引用该对象,否则当你释放引用它的对象然后创建新的对象引用它很可能导致App崩溃。

预防方案

对单例的Property不要使用strong,非要引用的话使用week

5.unrecognized selector

示例代码

- (void)testUnrecogernizedSelectorCash
{
    [self performSelector:@selector(testSel) withObject:nil afterDelay:0];
}

异常现象

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[ViewController testSel]: unrecognized selector sent to instance 0x7ffd41609d10'

预防方案

此类崩溃经常出现,特别是当服务器数据放回异常时,比如本来应该返回一个NSString类型字符串,结果返回NULL,当你调用字符串的length方式时,导致App崩溃。预防方法,重要的地方对类型进行判断再调用该类的相关方法,或者写一个分类统一处理此类逻辑。

6.操作tableView数据

示例代码

- (void)testTableViewUpdateCrash
{
    NSIndexPath *insertIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
    NSIndexPath *deleteIndexPath = [NSIndexPath indexPathForRow:1 inSection:0];
    NSIndexPath *reloadIndexPath = [NSIndexPath indexPathForRow:2 inSection:0];
    NSIndexPath *moved1IndexPath = [NSIndexPath indexPathForRow:3 inSection:0];
    NSIndexPath *moved2IndexPath = [NSIndexPath indexPathForRow:4 inSection:0];

    [self.tableView beginUpdates];
    
    [self.tableView insertRowsAtIndexPaths:@[insertIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView deleteRowsAtIndexPaths:@[deleteIndexPath]withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView reloadRowsAtIndexPaths:@[reloadIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
    [self.tableView moveRowAtIndexPath:moved1IndexPath toIndexPath:moved2IndexPath];
    
    [self.tableView endUpdates];
}

异常现象

Fatal Exception: NSInternalInconsistencyException
Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (1), plus or minus the number of sections inserted or deleted (1 inserted, 0 deleted).

预防方案

当需要动态更新tableView的数据时,计算好模型的数据使模型的数据和更新tableView的后的数据保持同步。

7.Push到同一个控制器多次

异常现象

Fatal Exception: NSInvalidArgumentException
Pushing the same view controller instance more than once is not supported (<PPSelectPayMethodViewControllerIOS7: 0x10d7e7f10>)

参考链接

以上就是工作中常见的异常崩溃以及处理方案,下面的异常分类内容来自Apple的官方文档,有兴趣的可以查阅。☕️

Apple官方常见异常类型(Exception types)

  1. 访问一块坏内存[EXC_BAD_ACCESS] // SIGSEGV // SIGBUS]
  2. 异常退出[EXC_CRASH // SIGABRT]
  3. 追踪受限[EXC_BREAKPOINT // SIGTRAP]
  4. 非法指令[EXC_BAD_INSTRUCTION // SIGILL]
  5. 被保护的资源遭到侵害[EXC_GUARD]
  6. 资源限制[EXC_RESOURCE]
  7. 其他异常类型

1.访问一块坏内存(Bad Memory Access)

当程序试图接入无效内容或者尝试以不被允许的方式接入由于内存的保护等级(例如,尝试写入只读的内存)。Exception Subtype字段包含一个kern_return_t结构体用来描述错误和不正确接入的内存地址。

下面是一些调试坏内存接入导致崩溃的建议:

  • 假如objc_msgSend或者objc_release在崩溃线程回溯(Backtraces)的顶部附近,这个线程可能尝试给一个释放的对象发消息。你应该profile应用使用Zombies instrument来更好的理解这个崩溃发生的条件。
  • 假如gpus_ReturnNotPermittedKillClient在崩溃线程回溯(Backtraces)的顶部附近,线程被终结因为它尝试用OpenGL ES或者Metal执行渲染当程序处于后台时。查看QA1766: How to fix OpenGL ES application crashes when moving to the background
  • 打开Address Sanitizer运行你的应用。Address Sanitizer添加了额外的说明在内容接入当你编译代码的时候。随着你应用的运行,Xcode将⚠️你假如内存以一种可能导致崩溃的方式接入。

2.异常退出(Abnormal Exit)

程序异常退出,是最常见导致这类异常崩溃的原因是捕获到Objective-C/C++异常和调用了abort()函数。

App Extensions将被终结发生这种类型的异常,假如他们初始化花费太多的时间(watchdog终结)。假如一个extension由于载入时间太长被终结,产生崩溃报告的Exception Subtype将是LAUNCH_HANG。因为extensions并没有一个main函数,任何花销在初始化的时间都发生在static constructors和呈现在你的extensions和依赖库的+load方法。你应该尽可能的延迟做这些工作。

3.追踪受限(Trace Trap)

和异常退出类似,这种异常的目的是给一个追加的调试器,让它有机会来打断在一个当它执行时候指定的点的进程。你可以使用__builtin_trap()函数在你的代码中来触发这个异常。假如没有调试器追加的话,线程将被终结并且产生一个崩溃报告。

低等级的库(例如libdispatch)将受限这个进程一旦遇到一个重大的错误。关于错误的额外信息可以在Additional Diagnostic Information章节中的崩溃报告找到,或者在设备的控制台。

假如在runtime遇到诸如下面的一个意外的条件,Swift代码将终结出现这种类型的异常:

  • 非可选类型带有一个nil
  • 错误的强制类型转换

查看Backtraces来决发生定异常条件的位置。额外的信息可能已经在设备的控制台打印出来了。你应该修改崩溃处的代码来优雅的处理runtime错误。例如,使用Optional Binding而不是强制解包一个可选变量。

4.非法指令(Illegal Instruction)

进程尝试执行一个非法或者未定义的指令。进程可能已经尝试跳进到一个无效的地址通过一个配置错误的函数指针。在Intel处理器中,ud2操作码导致一个EXC_BAD_INSTRUCTION异常,但是它通常被用来困住进程达到调试的目的。Swift代码在Intel处理器中将以这种异常终结,假如在runtime位置条件发生。更多详情查看Trace Trap

5.被保护的资源遭到侵害(Guarded Resource Violation)

进程侵犯一个被保护的资源。系统库可能某个文件的描述器成guarded,在那以后,所有不正常的操作在这些描述器上都将触发一个EXC_GUARD异常(当它想操作在这些文件描述器上,系统可以使用特殊的guarded标记的私有APIs)。这可以帮你向下快速追踪问题,例如关闭一个已经被系库打开的文件描述器。例如,假如一个app关闭文件秒杀器通过使用截图SQLite文件到一个Core Data存储,Core Data将会在随后诡异的崩溃。guard exception将让这些问题尽早引起你的注意,这样也让他们变得更容易调试。

崩溃报告来自新版的iOS包含了人类可读的详细信息关于引起EXC_GUARD异常的操作在Exception SubtypeException Message字段中。在来自macOS或者老版本的iOS的崩溃报告中,这些信息被编码到第一个Exception Code就像一个分解成如下的位段:

  • [63:61] - Guard Type:被保护的资源类型。0x2代表一个文件描述器资源。
  • [60:32] - Flavor:侵害被处罚时的条件
    • 假如第一个(1 << 0)位被设置,进程尝试执行close()函数在一个受保护的文件描述器。
    • 假如第二个(1 << 1)位被设置,进程尝试执行dup()dup2(),或者fcntl()F_DUPFD或者F_DUPFD_CLOEXEC命令在一个受保护的文件描述器。
    • 假如第三个(1 << 2)位被设置,进程尝试通过一个socket发送给一个受保护的文件描述器。
    • 假如第三个(1 << 3)位被设置,进程尝试写入到一个受保护的文件描述器。
  • [31:0] - File Descriptor:进程尝试修改的受保护的文件描述器。

6.资源限制(Resource Limit)

进程超出了一个资源消耗的限制。这是一个来自操作系统通知,告诉进程正在使用的资源过多。精确的资源列在Exception Subtype字段中。假如Exception Note字段包含NON-FATAL CONDITION,进程不会被终结即使产生了一个崩溃报告。

  • 异常子类型MEMORY表明进程已经越过系统应用的内存限制。这可能是一个终结的先兆由于超额的使用内存。

  • 异常子类型WAKEUPS表明在进程中的线程每秒被唤醒太多次,这强制CPU非常频繁的唤醒消耗电池寿命。

    典型的,这个通过由线程与线程的通信产生(通常是使用peformSelector:onThread:dispatch_async),那样无意的发生了远远超出它正常应该的切换频率。因为通信的协调发生得非常频繁而出发此类的异常,这个通常和多个后台线程有着相似Backtraces -- 表明那些地方发生过通信。

7.其他异常类型(Other Exception Types)

一些崩溃报告可能含有一个未命名的Exception Type,将以一个16进制的值(例如,00000020)的形式打印。假如你的设备收到了一个这样的崩溃报告,直接查看Exception Codes字段寻找更多的信息。

  • 异常代码0xbaaaaaad表明记录是整个系统的stackshot,不是一个崩溃报告。为了获得一个stackshot,按Home键和任意音量键。这些记录经常被用户偶然创建,并不表明是一个错误。

  • 异常代码0xbad22222表明一个VoIP应用已经被iOS终结,因为它启动得太频繁。

  • 异常代码0x8badf00d表明应用已经被iOS终结因为发生watchdog超时。应用花费太长时间启动,终结,或者响应系统事件。通常导致这歌问题是做了在主线程执行了同步的网络请求。无论什么操作在Thread 0都需要移动到后台线程,或者异步处理,以免它阻塞主线程。

  • 异常代码0xc00010ff表明引用被操作系统终结为了响应一个发热事件。这个可能由于一个发生崩溃的特定的设备的问题或者环境被操作导致。为了使你的应用更高效运行的建议,查看WWDC session iOS Performance and Power Optimization with Instruments

  • 异常代码0xdead10cc表明应用被iOS终结,由于当在后台运行时它持有了一个系统的资源(像通信录数据库)。

  • 异常代码0xdeadfa11表明应用被用户强制退出。强制退出发生在当用户第一次按下开关机按钮直到"滑动来关机"出现,然后在按下Home键。这是合理的假如用户这样做了,因为应用已经变得不可响应,但是这并不能保证 - 强制退出任何正在运行的任务。

    注意:终结一个挂起的app通过从多任务关系面板中移除并不会产生一个崩溃报告。一旦一个app被挂起,iOS它有资格在任何时候终结它,所有没有崩溃报告产生。

参考资料

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

推荐阅读更多精彩内容