iOS Crash 杀手排名

2024-06-05 18:32
文章标签 ios 排名 crash 杀手

本文主要是介绍iOS Crash 杀手排名,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

随着公司嘟嘟牛app用户数量多了起来,崩溃的问题也多了起来,最近这几天终于得空,集中时间处理了一下崩溃的问题,现总结一下,希望对大家有所帮助。

杀手 NO.1

NSInvalidArgumentException 异常

出现这个crash的原因有很多,选取了崩溃次数较多的crash。 

crash 日志1-1

-[__NSPlaceholderDictionary initWithObjects:forKeys:count:]: attempt to insert nil object from objects[3]

crash日志拿到了,怎么复现该现象呢?我们看到initWithObjects:forKeys:count:,猜测一下应该是NSDictionary初始化时的问题,在看后面的提示attempt to insert nil object,此时就可以做一个猜测,应该是NSDictionary初始化时插入nil对象造成的异常。下面我们写一段代码来验证一下:

NSString *password = nil;
NSDictionary *dict = @{@"userName": @"bruce",@"password": password};
NSLog(@"dict is : %@", dict);

运行过后,崩溃信息如下: 


Crash 日志1-1

上面的崩溃信息证明了我们的猜测。从崩溃日志记录中,查询到该问题的崩溃记录有33条(总崩溃记录304条),占10.85%,崩溃率比较高。为什么会出现这种现象呢?如何解决这样的crash呢?

崩溃率高的原因是因为自己的框架中采用了去model化的设计思想,不会把后台返回的数据转换成model,而是通过一个reformer机制转换成NSDictionary形式,提供给目标对象使用,在转换成NSDictionary的过程中,后台返回的数据有时可能为空,就会造成插入nil对象,从而导致crash。

有3种方案可以解决该问题,如下:

方案一:后台在返回数据的时候进行校验,对空值进行处理。但是在项目中有些空值是有特殊的用途,此种方案不可行。

方案二:在转换成NSDictionary的时候,对后台返回的数据进行校验,把空值转换成NSNull对象。方案可行,但是需要对现有代码做大的改动,每次转换的时候都需要进行校验,太麻烦。业务高速发展时期,这样做成本太高。

方案三:有没有一种无须改动现有代码又能解决该问题呢?答案是有的,可以利用Objective-C的runtime来解决该问题。

NSDictionary插入nil对象会造成崩溃,但是插入NSNull对象是不会造成崩溃的,只要利用runtime的Swizzle Method把nil对象给转换成NSNull对象就可以把该问题给解决了。创建一个NSDictionary的类别,利用runtime的Swizzle Method来替换系统的方法。源码实现可以参考Glow团队封装的NSDictionary+NilSafe(Github上可下载到), 全部源码会在文章末尾提供,现截取其中的部分代码如下:

+ (instancetype)gl_dictionaryWithObjects:(const id [])objects forKeys:(const id<NSCopying> [])keys count:(NSUInteger)cnt {id safeObjects[cnt];id safeKeys[cnt];NSUInteger j = 0;for (NSUInteger i = 0; i < cnt; i++) {id key = keys[i];id obj = objects[i];if (!key) {continue;}if (!obj) {obj = [NSNull null];}safeKeys[j] = key;safeObjects[j] = obj;j++;}return [self gl_dictionaryWithObjects:safeObjects forKeys:safeKeys count:j];
}

crash 日志1-2 

data parameter is nil

通过日志信息,可以把崩溃问题定位到参数为nil的情况,在看了下堆栈的日志信息,把问题定位到了NSJSONSerialization序列化的时候,传入data为nil,造成的崩溃。为了验证是不是该问题,我写了一段代码做了下验证:

NSData *data = nil;
NSError *error;
NSDictionary *orginDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
NSLog(@"originDict is : %@", orginDict);

运行后,崩溃信息如下:


Crash日志 1-2

这个问题比较好解决,在序列化的时候,统一加入判断,判断data是不是nil即可。

crash 日志1-3 

unrecognized selector sent to instance 0x15d23910

造成这条崩溃的原因,想必大家都比较熟悉了,就是一个类调用了一个不存在的方法,造成的崩溃。解决这样的问题,可以在写一个方法的时候,判断一下其类的类型,不符合类型的不让其调用,也可以使用runtime对常见的方法调用做一下错误兼容。比如我这边经常会出现这样的崩溃:

-[__NSCFConstantString objectForKeyedSubscript:]: unrecognized selector sent to instance 0x1741af420
-[NSNull length]: unrecognized selector sent to instance 0x1b21e6ef8    
-[__NSCFConstantString objectForKeyedSubscript:]: unrecognized selector sent to instance
-[__NSDictionaryI length]: unrecognized selector sent to instance 0x174264500

当这些对象调用这几个不存在的方法的时候,替换成自己定义的一个方法,对它们做一下错误兼容,使应用不会崩溃。现截取部分代码实现,全部源码会在文章末尾提供。 

@implementation NSString (NSRangeException)+ (void)load{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{@autoreleasepool {[objc_getClass("__NSCFConstantString") swizzleMethod:@selector(objectForKeyedSubscript:) swizzledSelector:@selector(replace_objectForKeyedSubscript:)];}});
}- (id)replace_objectForKeyedSubscript:(NSString *)key {return nil;
}@end

小结一下,造成NSInvalidArgumentException异常大概有以下原因: 

  • NSDictionary插入nil的对象。NSMutableDictionary也是同样的道理。
  • NSJSONSerialization序列化的时候,传入data为nil。
  • an unrecognized selector 无法识别的方法

NSInvalidArgumentException的崩溃记录有149条(总崩溃记录304条),占49.01%,称霸Crash界,杀手排名第一。

杀手 NO.2

SIGSEGV 异常

SIGSEGV是当SEGV发生的时候,让代码终止的标识。当去访问没有被开辟的内存或者已经被释放的内存时,就会发生这样的异常。另外,在低内存的时候,也可能会产生这样的异常。

对于这样的异常,我们可以使用两种方式来解决,一种方式使用Xcode自带的内存分析工具(Leaks),一种是使用facebook提供的自动化工具来监测内存泄漏问题,如:
FBRetainCycleDetector、FBAllocationTracker、FBMemoryProfiler

例子1: 

dataOut = malloc(dataOutAvailable * sizeof(uint8_t));

这是使用Xcode自带的Leaks工具检测到的内存泄漏,通过代码我们看出这是一个C语言使用malloc函数分配了一块内存地址,但是在不使用的时候却忘记了释放其内存地址,这样就造成了内存泄漏,应该在其不使用的时候加上如下代码:

free(dataOut);

另外,通过这个例子我们也要特别注意,在使用C语言对象的时候,一定要记得在不使用的时候给释放掉,ARC并不能释放掉这块内存。

例子2:

Can't add self as subview crash

造成这个崩溃的原因,一种原因是在push或pop一个视图的时候,并且设置了animated:YES,如果此时动画(animated)还没有完成,这个时候,你在去push或pop另外一个视图的时候,就会造成该异常。 也有其他原因可以造成这个崩溃,比如:

[self.view addSubview:self.view];

复现这个现象,我写了一个下面的代码测试,如下:

- (IBAction)btnAction:(id)sender {UIViewController *test01 = [[UIViewController alloc] init];[self.navigationController pushViewController:test01 animated:YES];[self.navigationController pushViewController:test01 animated:YES];
}

解决该异常最简单的方式是把animated设置为NO,但是很不友好,把系统自带的动画效果给去掉了。另外一种友好的方式就是通过runtime来进行实现了,通过安全的方式,确保当有控制器正在进行入栈或出栈时,没有其他入栈或出栈操作。具体源码会在文章末尾提供。

SIGSEGV的崩溃记录有57条(总共304条崩溃记录),占18.75%。在Crash界排名第二。

杀手 NO.3

NSRangeException 异常

造成这个异常,就是越界异常了,在iOS中我们经常碰到的越界异常有两种,一种是数组越界,一种字符串截取越界,我们通过crash日志来具体分析一下。

crash 日志3-1 

-[__NSArrayM objectAtIndex:]: index 1 beyond bounds for empty array
-[__NSCFConstantString substringToIndex:]: Index 10 out of bounds; string length 0

通过日志可以很明显的知道问题,就是越界造成的,复现该现象也比较简单,在此就略过了。怎么解决呢?

方案一:在对数组取数据的时候,要判断一下数组的长度大于取的index,这个要在平时写代码的时候给规范起来。同样在对字符串进行截取的时候,也需要做类似的判断。但现实的情况是,有时我们会忘了写这样的逻辑判断,就会有潜在的崩溃问题。如何做一下统一的判断呢?即使开发人员忘了写这样的逻辑判断也不会造成崩溃,从框架层面来杜绝这类的崩溃,方案二给出了答案。

方案二:利用runtime的Swizzle Method特性,可以实现从框架层面杜绝这类的崩溃问题,这样做的好处有两点:

  1. 开发人员忘了写判断越界的逻辑,也不会造成app的崩溃,对开发人员来说是透明的。
  2. 不需要修改现有的代码,对现有代码的侵入性降低到最低,不需要添加大量重复的逻辑判断代码。

全部源码会在文章末尾提供,现截取部分代码实现:

@implementation NSArray (NSRangeException)+ (void)load{static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{@autoreleasepool {[objc_getClass("__NSArray0") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(emptyObjectIndex:)];[objc_getClass("__NSArrayI") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(arrObjectIndex:)];[objc_getClass("__NSArrayM") swizzleMethod:@selector(objectAtIndex:) swizzledSelector:@selector(mutableObjectIndex:)];[objc_getClass("__NSArrayM") swizzleMethod:@selector(insertObject:atIndex:) swizzledSelector:@selector(mutableInsertObject:atIndex:)];}});
}- (id)emptyObjectIndex:(NSInteger)index{return nil;
}- (id)arrObjectIndex:(NSInteger)index{if (index >= self.count || index < 0) {return nil;}return [self arrObjectIndex:index];
}- (id)mutableObjectIndex:(NSInteger)index{if (index >= self.count || index < 0) {return nil;}return [self mutableObjectIndex:index];
}- (void)mutableInsertObject:(id)object atIndex:(NSUInteger)index{if (object) {[self mutableInsertObject:object atIndex:index];}
}@end

越界的崩溃记录有46条(总共崩溃记录是304条),占15.13%,在crash界杀手排名第三。

杀手 NO.4

SIGPIPE 异常

先解释一下什么是SIGPIPE异常,通俗一点的描述是这样的:对一个端已经关闭的socket调用两次write,第二次write将会产生SIGPIPE信号,该信号默认结束进程。

那如何解决该问题呢?对SIGPIPE信号可以进行捕获,也可将其忽略,对于iOS系统来说,只需要把下面这段代码放在.pch文件中即可。

// 仅在 IOS 系统上支持 SO_NOSIGPIPE
#if defined(SO_NOSIGPIPE) && !defined(MSG_NOSIGNAL)// We do not want SIGPIPE if writing to socket.const int value = 1;setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &value, sizeof(int));
#endif

SIGPIPE的崩溃记录有11条(总共304条崩溃记录),占3.61%。在Crash界排名第四。

杀手 NO.5

SIGABRT 异常

这是一个让程序终止的标识,会在断言、app内部、操作系统用终止方法抛出。通常发生在异步执行系统方法的时候。如CoreData、NSUserDefaults等,还有一些其他的系统多线程操作。

注意:这并不一定意味着是系统代码存在bug,代码仅仅是成了无效状态,或者异常状态。

SIGABRT崩溃记录9条(总共304条崩溃记录),占2.96%。Crash界排名第五。

杀手总结

前面5大crash杀手,占了89.46%的崩溃率,解决了这5大crash杀手,基本上你的app就很健壮了,剩下的崩溃问题就需要具体问题具体分析了。

转载链接:http://www.jianshu.com/p/c7efbc283480

这篇关于iOS Crash 杀手排名的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1033835

相关文章

安卓链接正常显示,ios#符被转义%23导致链接访问404

原因分析: url中含有特殊字符 中文未编码 都有可能导致URL转换失败,所以需要对url编码处理  如下: guard let allowUrl = webUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {return} 后面发现当url中有#号时,会被误伤转义为%23,导致链接无法访问

hdu 2093 考试排名(sscanf)

模拟题。 直接从教程里拉解析。 因为表格里的数据格式不统一。有时候有"()",有时候又没有。而它也不会给我们提示。 这种情况下,就只能它它们统一看作字符串来处理了。现在就请出我们的主角sscanf()! sscanf 语法: #include int sscanf( const char *buffer, const char *format, ... ); 函数sscanf()和

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

iOS剪贴板同步到Windows剪贴板(无需安装软件的方案)

摘要 剪贴板同步能够提高很多的效率,免去复制、发送、复制、粘贴的步骤,只需要在手机上复制,就可以直接在电脑上 ctrl+v 粘贴,这方面在 Apple 设备中是做的非常好的,Apple 设备之间的剪贴板同步功能(Universal Clipboard)确实非常方便,它可以在 iPhone、iPad 和 Mac 之间无缝传输剪贴板内容,从而大大提高工作效率。 但是,iPhone 如何和 Wind

iOS项目发布提交出现invalid code signing entitlements错误。

1、进入开发者账号,选择App IDs,找到自己项目对应的AppId,点击进去编辑, 2、看下错误提示出现  --Specifically, value "CVYZ6723728.*" for key "com.apple.developer.ubiquity-container-identifiers" in XX is not supported.-- 这样的错误提示 将ubiquity

我的第一次份实习工作-iOS实习生-第三个月

第三个月 这个月有一个考核项目,是一个电子书阅读器,组长说很重要,是我的实习考核项目。 我的项目XTReader,这是我参考网上的一些代码,和模仿咪咕阅读做的,功能还不完善,数据的部分是用聚合数据做的。要收费的。   还有阅读页面,基本功能实现了一下。使用了autolayout,自适应布局,也是第一次用网络,第一次用数据库,第一次用自动布局。还有很多不足。 做了一周多,有个问题一直没

我的第一次份实习工作-iOS实习生-公司使用过的软件

bittorrentsync 素材,文件同步软件 cornerstone svn 软件开发合作 mark man 测量坐标的软件 SQLLite Manager 数据库操作软件

我的第一次份实习工作-iOS实习生-第二个月

第二个月 来公司过了一个月了。每天早上9点上班,到晚上6.30下班,上下班要指纹打卡,第一个月忘了打卡好多次(),然后还要去补打卡单。公司这边还安排了,工资卡办理,招商银行卡。开了一次新员工大会,认识了公司的一些过往,公司的要求等,还加了一下公司的企业QQ,还有其他的羽毛球群,篮球群。我加了下羽毛球群,也去打了一两次。第二个月的感受,感觉跟组里面的交流跟沟通都好少,基本上还有好多人不认识。想想也

我的第一次份实习工作-iOS实习生-第一个月

实习时间:2015-08-20 到 2015-12-25  实习公司;福建天棣互联有限公司 实习岗位:iOS开发实习生 第一个月: 第一天来公司,前台报道后,人资带我去我工作的地方。到了那,就由一个组长带我,当时还没有我的办公桌,组长在第三排给我找了一个位置,擦了下桌子,把旁边的准备的电脑帮我装了下,因为学的是iOS,实习生就只能用黑苹果了,这是我实习用的电脑。 帮我装了一下电脑后,开机

iOS如何隐藏系统状态栏

这里主要说明一下iOS7系统给状态栏的适配及隐藏带来的改变。 变化一: 不隐藏状态栏的情况下,StatusBar会直接显示在当前页面上,当前页面的会延伸到 StatusBar下方,顶到最上头。 这种显示方式在iOS7上是无法改变的,也无法通过设置或者配置类达到iOS6的状态栏效果。       所以在iOS7上进行页面布局的时候要考虑