程序崩溃经历

其实在很早之前就想写这篇文章了,一直拖到现在。

  • 程序崩溃经历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相关推荐

  1. iOS runtime实用篇:让你快速上手一个项目

    2019独角兽企业重金招聘Python工程师标准>>> 前言: 对于一个大项目而言,最烦恼的就是在众多界面难以找到对应的viewController,要改个东西都要花好长的时间去找对 ...

  2. iOS直播实用篇(手把手教)

    一.简述总体内容 1.直播流程介绍 2.Mac搭建nginx+rtmp服务器(模拟推流拉流) 3.简单的集成推流拉流(实用篇) 4.好的博客推荐 二.直播流程介绍 1.简单的流程图 简单的流程图 2. ...

  3. iOS开发UI篇—常见的项目文件介绍

    iOS开发UI篇-常见的项目文件介绍 一.项目文件结构示意图 二.文件介绍 1.products文件夹:主要用于mac电脑开发的可执行文件,ios开发用不到这个文件 2.frameworks文件夹主要 ...

  4. 普元EOS开发积累第一篇(常见错误解决方法) 持续更新

    普元EOS开发积累第一篇(常见错误解决方法) 持续更新 参考文章: (1)普元EOS开发积累第一篇(常见错误解决方法) 持续更新 (2)https://www.cnblogs.com/tangjing ...

  5. 计算机主板最常见的问题,计算机主板功能 电脑实用技巧解决常见问题

    电脑实用技巧解决常见问题 1.解决问题 插电即开机问题 现象:有些朋友有关机后断开电源板电源的习惯,可是却常常被一个问题困扰,就是电源板一通电,计算机就自动开机了,Power键形同虚设.解决问题:有些 ...

  6. iOS runtime 学习分享

    这是团队小伙伴在内部的一次技术分享, 很开心, 我们团队越来越好了. iOS runtime 学习分享 Author:Liao Zusheng 申明: 部分资料来自于知名论坛和博客,已在文中给出相关源 ...

  7. iOS开发中,如何防止Crash(闪退,崩溃)?

    ** 前言 ** 移动APP中关于crash几乎是0容忍的,那么iOS中会有很多引起crash,比如最常见的数组越界,添加空值. 如果你想解决大部分可能引起的crash,保持代码的健壮性,又不想修改太 ...

  8. iOS开发网络篇—数据缓存

    iOS开发网络篇-数据缓存 一.关于同一个URL的多次请求 有时候,对同一个URL请求多次,返回的数据可能都是一样的,比如服务器上的某张图片,无论下载多少次,返回的数据都是一样的. 上面的情况会造成以 ...

  9. SpringCloud实用篇01

    SpringCloud实用篇资料下载 1.认识微服务 随着互联网行业的发展,对服务的要求也越来越高,服务架构也从单体架构逐渐演变为现在流行的微服务架构.这些架构之间有怎样的差别呢? 1.0.学习目标 ...

  10. 微服务框架springcloud(实用篇)【5】Elasticsearch 01

    一.初识elasticsearch 1.了解ES 1)elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎,具备非常多强大功能,可以帮助我们从海量数据中快速找到需要 ...

最新文章

  1. python 聚类_使用python+sklearn实现聚类性能评估中随机分配对聚类度量值的影响
  2. 科研文献|季节变化是流域尺度上土壤抗性变化的主要驱动因素
  3. 目的:使用CUDA环境变量CUDA_VISIBLE_DEVICES来限定CUDA程序所能使用的GPU
  4. hadoop + spark+ hive 集群搭建(apache版本)
  5. python单元测试之unittest框架使用
  6. Ubuntu 21.04终端中文乱码和无法输入中文
  7. 利用STL离散化处理数据(unique)
  8. 深度学习——02、深度学习入门——python实现RNN算法
  9. 华为云开发者青年班——你的优秀值得被全球开发者看到!
  10. bat脚本实现微信多开
  11. 机器学习实战 2.3获取数据
  12. Linux: Manjaro/Arch logiops 罗技鼠标驱动安装教程
  13. Http 资源服务器 搭建 HFS
  14. IDEA中如何进行XML解析
  15. 深度强化学习方法(DQN)玩转Atari游戏(pong)
  16. 上周热点回顾(7.4-7.10)
  17. photoshop怎么设计淘宝天猫海报amp;nb…
  18. Mac 上怎么双开微信
  19. 人生有如负重致远,不可急躁
  20. 嵌入式的可移植性和可复用性

热门文章

  1. 湖南联通云计算与IDC掀发展浪潮
  2. [转]响应式web设计之CSS3 Media Queries
  3. CPU虚拟化技术解析
  4. MyEclipse创建Maven工程
  5. 使用Intellij Idea生成可执行文件jar,转为exe文件步骤
  6. [2018.09.08 T2] 最大土地面积
  7. HDU2897 邂逅明下
  8. js高级学习笔记(b站尚硅谷)-12-执行上下文
  9. nginx的安装及配置文件详解
  10. 刘明计算机学院,西南大学计算机与信息科学学院研究生导师简介-刘明