iOS ios 程序员

App常见崩溃问题分析

2016-10-23  本文已影响4682人  Oneruofeng

前言

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

App常见崩溃

  1. 数组下标越界

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结构体用来描述错误和不正确接入的内存地址。

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

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代码将终结出现这种类型的异常:

查看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就像一个分解成如下的位段:

6.资源限制(Resource Limit)

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

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

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

参考资料

上一篇下一篇

猜你喜欢

热点阅读