iOS runtime实用篇解决常见Crash
程序崩溃经历
其实在很早之前就想写这篇文章了,一直拖到现在。
程序崩溃经历1
平时开发测试的时候好好的,结果上线几天发现有崩溃的问题,其实责任大部分在我身上。
我的责任: 过分信赖文档,没进行容错处理,也就是没有对数据进行相应的判断处理。
下面附上代码,说明崩溃的原因
因第三方公司提供的数据错乱导致有时候创建字典的时候个别value为nil才导致的崩溃
//宏#define CStringToOcString(cstr) [NSString stringWithCString:cstr encoding:GBK_ENCODE]//将每组数据都保存起来NSMutableArray *returnArray = [NSMutableArray array];for (int i = 0; i < recordM.count; i++) {Withdrawqry_entrust_record *record = (Withdrawqry_entrust_record *)alloca(sizeof(Withdrawqry_entrust_record)); memset(record, 0x00, sizeof(Withdrawqry_entrust_record));[[recordM objectAtIndex:i] getValue:record]; //崩溃的原因在创建字典的时候,有个别value为nil (CStringToOcString)NSDictionary *param = @{ @"batch_no" : CStringToOcString(record->batch_no),// 委托批号@"entrust_no" : CStringToOcString(record->entrust_no),// 委托编号@"entrust_type" : @(record->entrust_type),//委托类别 6 融资委托 7 融券委托 和entrust_bs结合形成融资买入,融资卖出,融券卖出,融券买入@"entrust_bs" : @(record->entrust_bs),// 买卖标志@"stock_account" : CStringToOcString(record->stock_account),//证券账号@"gdcode" : CStringToOcString(record->gdcode),...............};
解决办法,在宏那里做了个判断,若果value为nil,直接赋值为@""
#define CStringToOcString(cstr) [NSString stringWithCString:cstr encoding:GBK_ENCODE] ? [NSString stringWithCString:cstr encoding:GBK_ENCODE] : @""
程序崩溃经历2
不做过多的阐述,直接看代码
//服务器返回的日期格式为20160301//我要将格式转换成2016-03-01/** 委托日期 */NSMutableString *dateStrM = 服务器返回的数据[dateStrM insertString:@"-" atIndex:4];[dateStrM insertString:@"-" atIndex:7];
就是上面的代码导致了上线的程序崩溃,搞的我在第二天紧急再上线了一个版本。
为何会崩溃呢?原因是服务器返回的数据错乱了,返回了0。这样字符串的长度就为1,而却插入下标为4的位置,程序必然会崩溃。后来在原本代码上加了一个判断,如下代码:
if (dateStrM.length >= 8) {[dateStrM insertString:@"-" atIndex:4];[dateStrM insertString:@"-" atIndex:7];}
醒悟
1、不要过分相信服务器返回的数据会永远的正确。
2、在对数据处理上,要进行容错处理,进行相应判断之后再处理数据,这是一个良好的编程习惯。
思考:如何防止存在潜在崩溃方法的崩溃
众所周知,Foundation框架里有非常多常用的方法有导致崩溃的潜在危险。对于一个已经将近竣工的项目,若起初没做容错处理又该怎么办?你总不会一行行代码去排查有没有做容错处理吧!-------- 别逗逼了,老板催你明天就要上线了!
那有没有一种一劳永逸的方法?无需动原本的代码就可以解决潜在崩溃的问题呢?
解决方案
拦截存在潜在崩溃危险的方法,在拦截的方法里进行相应的处理,就可以防止方法的崩溃
步骤:
1、通过category给类添加方法用来替换掉原本存在潜在崩溃的方法。
2、利用runtime方法交换技术,将系统方法替换成我们给类添加的新方法。
3、利用异常的捕获来防止程序的崩溃,并且进行相应的处理。
如果对异常NSException不了解,可以点击查看NSException的介绍。
具体实现
创建一个工具类AvoidCrash,来处理方法的交换,获取会导致崩溃代码的具体位置,在控制台输出错误的信息......
代码中有正则表达式的知识点,不熟悉正则表达式的朋友们点我
AvoidCrash.h
AvoidCrash.h// AvoidCrash Created by mac on 16/9/21.// Copyright ? 2016年 chenfanfang. All rights reserved.//#import #import //通知的名称,若要获取详细的崩溃信息,请监听此通知#define AvoidCrashNotification @"AvoidCrashNotification"#define AvoidCrashDefaultReturnNil @"This framework default is to return nil."#define AvoidCrashDefaultIgnore @"This framework default is to ignore this operation to avoid crash."@interface AvoidCrash : NSObject/*** become effective . You can call becomeEffective method in AppDelegate didFinishLaunchingWithOptions* * 开始生效.你可以在AppDelegate的didFinishLaunchingWithOptions方法中调用becomeEffective方法*/+ (void)becomeEffective;+ (void)exchangeClassMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel;+ (void)exchangeInstanceMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel;+ (NSString *)getMainCallStackSymbolMessageWithCallStackSymbolStr:(NSString *)callStackSymbolStr;+ (void)noteErrorWithException:(NSException *)exception defaultToDo:(NSString *)defaultToDo;@end
AvoidCrash.m
AvoidCrash.m// AvoidCrash Created by mac on 16/9/21.// Copyright ? 2016年 chenfanfang. All rights reserved.//#import "AvoidCrash.h"//category#import "NSArray+AvoidCrash.h"#import "NSMutableArray+AvoidCrash.h"#import "NSDictionary+AvoidCrash.h"#import "NSMutableDictionary+AvoidCrash.h"#import "NSString+AvoidCrash.h"#import "NSMutableString+AvoidCrash.h"#define AvoidCrashSeparator @"================================================================"#define AvoidCrashSeparatorWithFlag @"========================AvoidCrash Log=========================="#define key_errorName @"errorName"#define key_errorReason @"errorReason"#define key_errorPlace @"errorPlace"#define key_defaultToDo @"defaultToDo"#define key_callStackSymbols @"callStackSymbols"#define key_exception @"exception"@implementation AvoidCrash/*** 开始生效(进行方法的交换)*/+ (void)becomeEffective { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{[NSArray avoidCrashExchangeMethod];[NSMutableArray avoidCrashExchangeMethod];[NSDictionary avoidCrashExchangeMethod];[NSMutableDictionary avoidCrashExchangeMethod];[NSString avoidCrashExchangeMethod];[NSMutableString avoidCrashExchangeMethod];}); }/*** 类方法的交换** @param anClass 哪个类* @param method1Sel 方法1* @param method2Sel 方法2*/+ (void)exchangeClassMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {Method method1 = class_getClassMethod(anClass, method1Sel);Method method2 = class_getClassMethod(anClass, method2Sel);method_exchangeImplementations(method1, method2); }/*** 对象方法的交换** @param anClass 哪个类* @param method1Sel 方法1* @param method2Sel 方法2*/+ (void)exchangeInstanceMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {Method method1 = class_getInstanceMethod(anClass, method1Sel);Method method2 = class_getInstanceMethod(anClass, method2Sel);method_exchangeImplementations(method1, method2); }/*** 获取堆栈主要崩溃精简化的信息<根据正则表达式匹配出来>** @param callStackSymbolStr 堆栈主要崩溃信息** @return 堆栈主要崩溃精简化的信息*/+ (NSString *)getMainCallStackSymbolMessageWithCallStackSymbolStr:(NSString *)callStackSymbolStr { //不熟悉正则表达式的朋友,可以看我另外一篇文章,链接在下面//http://www.jianshu.com/p/b25b05ef170d//mainCallStackSymbolMsg的格式为 +[类名 方法名] 或者 -[类名 方法名]__block NSString *mainCallStackSymbolMsg = nil; //匹配出来的格式为 +[类名 方法名] 或者 -[类名 方法名]NSString *regularExpStr = @"[-\\+]\\[.+\\]"; NSRegularExpression *regularExp = [[NSRegularExpression alloc] initWithPattern:regularExpStr options:NSRegularExpressionCaseInsensitive error:nil];[regularExp enumerateMatchesInString:callStackSymbolStr options:NSMatchingReportProgress range:NSMakeRange(0, callStackSymbolStr.length) usingBlock:^(NSTextCheckingResult * _Nullable result, NSMatchingFlags flags, BOOL * _Nonnull stop) { if (result) {mainCallStackSymbolMsg = [callStackSymbolStr substringWithRange:result.range];*stop = YES;}}]; return mainCallStackSymbolMsg; }/*** 提示崩溃的信息(控制台输出、通知)** @param exception 捕获到的异常* @param defaultToDo 这个框架里默认的做法*/+ (void)noteErrorWithException:(NSException *)exception defaultToDo:(NSString *)defaultToDo { //堆栈数据NSArray *callStackSymbolsArr = [NSThread callStackSymbols]; //获取在哪个类的哪个方法中实例化的数组 字符串格式 -[类名 方法名] 或者 +[类名 方法名]NSString *mainCallStackSymbolMsg = [AvoidCrash getMainCallStackSymbolMessageWithCallStackSymbolStr:callStackSymbolsArr[2]]; if (mainCallStackSymbolMsg == nil) {mainCallStackSymbolMsg = @"崩溃方法定位失败,请您查看函数调用栈来排查错误原因";} NSString *errorName = exception.name; NSString *errorReason = exception.reason; //errorReason 可能为 -[__NSCFConstantString avoidCrashCharacterAtIndex:]: Range or index out of bounds//将avoidCrash去掉errorReason = [errorReason stringByReplacingOccurrencesOfString:@"avoidCrash" withString:@""]; NSString *errorPlace = [NSString stringWithFormat:@"Error Place:%@",mainCallStackSymbolMsg]; NSString *logErrorMessage = [NSString stringWithFormat:@"\n\n%@\n\n%@\n%@\n%@\n%@\n\n%@\n\n",AvoidCrashSeparatorWithFlag, errorName, errorReason, errorPlace, defaultToDo, AvoidCrashSeparator]; NSLog(@"%@", logErrorMessage); NSDictionary *errorInfoDic = @{key_errorName : errorName,key_errorReason : errorReason,key_errorPlace : errorPlace,key_defaultToDo : defaultToDo,key_exception : exception,key_callStackSymbols : callStackSymbolsArr}; //将错误信息放在字典里,用通知的形式发送出去[[NSNotificationCenter defaultCenter] postNotificationName:AvoidCrashNotification object:nil userInfo:errorInfoDic]; }@end
创建一个NSDictionary的分类,来防止创建一个字典而导致的崩溃。
NSDictionary+AvoidCrash.h
NSDictionary+AvoidCrash.h// AvoidCrash Created by mac on 16/9/21.// Copyright ? 2016年 chenfanfang. All rights reserved.//#import @interface NSDictionary (AvoidCrash)+ (void)avoidCrashExchangeMethod;@end
NSDictionary+AvoidCrash.m
在这里先补充一个知识点: 我们平常用的快速创建字典的方式@{key : value}; 其实调用的方法是dictionaryWithObjects:forKeys:count:
而该方法可能导致崩溃的原因为: key数组中的key或者objects中的value为空
NSDictionary+AvoidCrash.m// AvoidCrash Created by mac on 16/9/21.// Copyright ? 2016年 chenfanfang. All rights reserved.//#import "NSDictionary+AvoidCrash.h"#import "AvoidCrash.h"@implementation NSDictionary (AvoidCrash)+ (void)avoidCrashExchangeMethod {[AvoidCrash exchangeClassMethod:self method1Sel:@selector(dictionaryWithObjects:forKeys:count:) method2Sel:@selector(avoidCrashDictionaryWithObjects:forKeys:count:)]; }+ (instancetype)avoidCrashDictionaryWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt { id instance = nil; @try {instance = [self avoidCrashDictionaryWithObjects:objects forKeys:keys count:cnt];} @catch (NSException *exception) { NSString *defaultToDo = @"This framework default is to remove nil key-values and instance a dictionary.";[AvoidCrash noteErrorWithException:exception defaultToDo:defaultToDo]; //处理错误的数据,然后重新初始化一个字典NSUInteger index = 0; id _Nonnull __unsafe_unretained newObjects[cnt]; id _Nonnull __unsafe_unretained newkeys[cnt]; for (int i = 0; i < cnt; i++) { if (objects[i] && keys[i]) {newObjects[index] = objects[i];newkeys[index] = keys[i];index++;}}instance = [self avoidCrashDictionaryWithObjects:newObjects forKeys:newkeys count:index];} @finally { return instance;} }@end
来看下防止崩溃的效果
正常情况下,若没有我们上面的处理,如下代码就会导致崩溃
NSString *nilStr = nil; NSDictionary *dict = @{ @"key" : nilStr};
崩溃截图如下:
若通过如上的处理,就可以避免崩溃了
[AvoidCrash becomeEffective];
控制台的输出截图如下
若想要获取到崩溃的详细信息(我们可以监听通知,通知名为:AvoidCrashNotification):可以将这些信息传到我们的服务器,或者在集成第三方收集Crash信息的SDK中自定义信息,这样我们就可以防止程序的崩溃,并且又得知哪些代码导致了崩溃。
//监听通知:AvoidCrashNotification, 获取AvoidCrash捕获的崩溃日志的详细信息[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dealwithCrashMessage:) name:AvoidCrashNotification object:nil];- (void)dealwithCrashMessage:(NSNotification *)note { //注意:所有的信息都在userInfo中//你可以在这里收集相应的崩溃信息进行相应的处理(比如传到自己服务器)NSLog(@"\n\n在AppDelegate中 方法:dealwithCrashMessage打印\n\n\n\n\n%@\n\n\n\n",note.userInfo); }
附上一张截图查看通知中携带的崩溃信息是如何的
结束语
程序崩溃有崩溃的好处,就是让开发者快速认识到自己所写的代码有问题,这样才能及时修复BUG,当然这种好处只限于在开发阶段。若一个上线APP出现崩溃的问题,这问题可就大了(老板不高兴,后果很严重)。
个人建议:在发布的时候APP的时候再用上面介绍的方法来防止程序的崩溃,在开发阶段最好不用。
上面只是举个例子,更多防止崩溃的方法请查看Github源码 AvoidCrash,这是我最近写的一个框架,大家可以集成到自己的项目中去,在发布APP的时候在appDelegate的didFinishLaunchingWithOptions中调用方法
[AvoidCrash becomeEffective];
即可,若要获取崩溃信息,监听通知即可。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {[AvoidCrash becomeEffective]; //监听通知:AvoidCrashNotification, 获取AvoidCrash捕获的崩溃日志的详细信息[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dealwithCrashMessage:) name:AvoidCrashNotification object:nil]; return YES; }- (void)dealwithCrashMessage:(NSNotification *)note { //注意:所有的信息都在userInfo中//你可以在这里收集相应的崩溃信息进行相应的处理(比如传到自己服务器)NSLog(@"\n\n在AppDelegate中 方法:dealwithCrashMessage打印\n\n\n\n\n%@\n\n\n\n",note.userInfo); }
同时希望大家能够提出更多容易导致崩溃的方法,我好添加到AvoidCrash框架中,当然也欢迎大家和我一起维护这个框架。
最后,希望大家给上你们珍贵的一票(帅哥、美女,给个star哈)。
AvoidCrash更新
2016-10-15
修复上一个版本部分方法不能拦截崩溃的BUG,具体修复哪些可以查看issues和简书上的留言。
优化崩溃代码的定位,定位崩溃代码更加准确。
增加对KVC赋值防止崩溃的处理。
增加对NSAttributedString防止崩溃的处理
增加对NSMutableAttributedString防止崩溃的处理
源码
https://github.com/chenfanfang/AvoidCrash
转载于:https://www.cnblogs.com/pioneerMax/p/6090404.html
iOS runtime实用篇解决常见Crash相关推荐
- iOS runtime实用篇:让你快速上手一个项目
2019独角兽企业重金招聘Python工程师标准>>> 前言: 对于一个大项目而言,最烦恼的就是在众多界面难以找到对应的viewController,要改个东西都要花好长的时间去找对 ...
- iOS直播实用篇(手把手教)
一.简述总体内容 1.直播流程介绍 2.Mac搭建nginx+rtmp服务器(模拟推流拉流) 3.简单的集成推流拉流(实用篇) 4.好的博客推荐 二.直播流程介绍 1.简单的流程图 简单的流程图 2. ...
- iOS开发UI篇—常见的项目文件介绍
iOS开发UI篇-常见的项目文件介绍 一.项目文件结构示意图 二.文件介绍 1.products文件夹:主要用于mac电脑开发的可执行文件,ios开发用不到这个文件 2.frameworks文件夹主要 ...
- 普元EOS开发积累第一篇(常见错误解决方法) 持续更新
普元EOS开发积累第一篇(常见错误解决方法) 持续更新 参考文章: (1)普元EOS开发积累第一篇(常见错误解决方法) 持续更新 (2)https://www.cnblogs.com/tangjing ...
- 计算机主板最常见的问题,计算机主板功能 电脑实用技巧解决常见问题
电脑实用技巧解决常见问题 1.解决问题 插电即开机问题 现象:有些朋友有关机后断开电源板电源的习惯,可是却常常被一个问题困扰,就是电源板一通电,计算机就自动开机了,Power键形同虚设.解决问题:有些 ...
- iOS runtime 学习分享
这是团队小伙伴在内部的一次技术分享, 很开心, 我们团队越来越好了. iOS runtime 学习分享 Author:Liao Zusheng 申明: 部分资料来自于知名论坛和博客,已在文中给出相关源 ...
- iOS开发中,如何防止Crash(闪退,崩溃)?
** 前言 ** 移动APP中关于crash几乎是0容忍的,那么iOS中会有很多引起crash,比如最常见的数组越界,添加空值. 如果你想解决大部分可能引起的crash,保持代码的健壮性,又不想修改太 ...
- iOS开发网络篇—数据缓存
iOS开发网络篇-数据缓存 一.关于同一个URL的多次请求 有时候,对同一个URL请求多次,返回的数据可能都是一样的,比如服务器上的某张图片,无论下载多少次,返回的数据都是一样的. 上面的情况会造成以 ...
- SpringCloud实用篇01
SpringCloud实用篇资料下载 1.认识微服务 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构.这些架构之间有怎样的差别呢? 1.0.学习目标 ...
- 微服务框架springcloud(实用篇)【5】Elasticsearch 01
一.初识elasticsearch 1.了解ES 1)elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要 ...
最新文章
- python 聚类_使用python+sklearn实现聚类性能评估中随机分配对聚类度量值的影响
- 科研文献|季节变化是流域尺度上土壤抗性变化的主要驱动因素
- 目的:使用CUDA环境变量CUDA_VISIBLE_DEVICES来限定CUDA程序所能使用的GPU
- hadoop + spark+ hive 集群搭建(apache版本)
- python单元测试之unittest框架使用
- Ubuntu 21.04终端中文乱码和无法输入中文
- 利用STL离散化处理数据(unique)
- 深度学习——02、深度学习入门——python实现RNN算法
- 华为云开发者青年班——你的优秀值得被全球开发者看到!
- bat脚本实现微信多开
- 机器学习实战 2.3获取数据
- Linux: Manjaro/Arch logiops 罗技鼠标驱动安装教程
- Http 资源服务器 搭建 HFS
- IDEA中如何进行XML解析
- 深度强化学习方法(DQN)玩转Atari游戏(pong)
- 上周热点回顾(7.4-7.10)
- photoshop怎么设计淘宝天猫海报amp;nb…
- Mac 上怎么双开微信
- 人生有如负重致远,不可急躁
- 嵌入式的可移植性和可复用性