美文网首页iOS Collection橙红科技有限公司复制粘贴
iOS runtime实用篇--和常见崩溃say good-by

iOS runtime实用篇--和常见崩溃say good-by

作者: chenfanfang | 来源:发表于2016-10-06 23:54 被阅读8541次
    say no.jpg

    源码

    https://github.com/chenfanfang/AvoidCrash


    程序崩溃经历

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

    • 程序崩溃经历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的介绍。](http://www.jianshu.com/p/05aad21e319e)
    
    
    
    
    ---
    具体实现
    ===
    创建一个工具类AvoidCrash,来处理方法的交换,获取会导致崩溃代码的具体位置,在控制台输出错误的信息......
    - [代码中有正则表达式的知识点,不熟悉正则表达式的朋友们点我](http://www.jianshu.com/p/b25b05ef170d)
    
    `AvoidCrash.h`
    
    

    //
    // AvoidCrash.h
    // AvoidCrash
    //
    // Created by mac on 16/9/21.
    // Copyright © 2016年 chenfanfang. All rights reserved.
    //

    import <Foundation/Foundation.h>

    import <objc/runtime.h>

    //通知的名称,若要获取详细的崩溃信息,请监听此通知

    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 <Foundation/Foundation.h>

    @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<NSCopying> _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
                           };
    
    
    崩溃截图如下:
    
    ![崩溃截图.png](http:https://img.haomeiwen.com/i1594675/f6dbad6c12d275b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    ---
    
    - 若通过如上的处理,就可以避免崩溃了
    

    [AvoidCrash becomeEffective];

    
    控制台的输出截图如下
    
    ![防止崩溃控制台输出的信息.png](http:https://img.haomeiwen.com/i1594675/2622b50e13cbd022.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    - 若想要获取到崩溃的详细信息(我们可以监听通知,通知名为: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);
      }

    
    附上一张截图查看通知中携带的崩溃信息是如何的
    
    ![AvoidCrashNotification通知的监听.png](http:https://img.haomeiwen.com/i1594675/284b0813751e725c.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
    
    
    
    ---
    结束语
    ===
    - 程序崩溃有崩溃的好处,就是让开发者快速认识到自己所写的代码有问题,这样才能及时修复BUG,当然这种好处只限于在开发阶段。若一个上线APP出现崩溃的问题,这问题可就大了(老板不高兴,后果很严重)。
    
    - 个人建议:在发布的时候APP的时候再用上面介绍的方法来防止程序的崩溃,在开发阶段最好不用。
    
    - 上面只是举个例子,更多防止崩溃的方法请查看Github源码 [AvoidCrash](https://github.com/chenfanfang/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](https://github.com/chenfanfang/AvoidCrash)更新
    ===
    ####2016-10-15
    - 修复上一个版本部分方法不能拦截崩溃的BUG,具体修复哪些可以查看issues和简书上的留言。
    - 优化崩溃代码的定位,定位崩溃代码更加准确。
    - 增加对KVC赋值防止崩溃的处理。
    - 增加对NSAttributedString防止崩溃的处理
    - 增加对NSMutableAttributedString防止崩溃的处理
    
    
    ####2016-11-29
    
    - 修复在键盘弹出状态下,按Home键进入后台会导致崩溃的bug。
    - 新增防止崩溃(NSArray、NSMutableArray) - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes
    
    
    #### 2016-12-1
    - 处理数组的类簇问题,提高兼容性,不论是由于array[100]方式,还是[array objectAtIndex:100]方式 获取数组中的某个元素操作不当而导致的crash,都能被拦截防止崩溃。
     - 上一个版本只能防止array[100]导致的崩溃,不能防止[array objectAtIndex:100]导致的崩溃。
    
    - 统一对线程进行处理,监听通知AvoidCrashNotification后,不论是在主线程导致的crash还是在子线程导致的crash,监听通知的方法统一在"主线程"中。
     - 上一个版本中,在哪个线程导致的crash, 则监听通知的方法就在哪个线程中。
    
    - 新增防止崩溃 (NSArray、NSMutableArray) `- (void)getObjects:(__unsafe_unretained id  _Nonnull *)objects range:(NSRange)range`
    

    相关文章

      网友评论

      • 啊左:虽然你写得很乱,但是大概知道你要表达什么,哈哈。
      • 95bb1f8adaf4:(instancetype)avoidCrashDictionaryWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt {

        id instance = nil;

        @try {
        instance = [self avoidCrashDictionaryWithObjects:objects forKeys:keys count:cnt];
        }
        @catch (NSException *exception) {
        }
        这样调用为什么不会死循环?
      • Ilovecoding822:类方法没实现可以一并处理么?
      • 一滴一泪一:不错,但是函数不存在的崩溃没有处理
      • 小草先生:说说我这几年接触到如何处理崩溃的:
        1、首先集成Bugly 自动上传符号表,这样不管debug 还是上线版本的崩溃都能捕捉到,但是有时崩溃在第三方SDK里面 难以处理。
        2、NSArray、NSDictionary、NSNumber 取值和赋值的崩溃可以用 写分类 判空函数 来避免。
        3、第2条也可以用runtime + 分类 要捕捉到NSException 来处理崩溃
        4、Swift 的 ? 有解包过程,是类型安全的,写法没问题一般不会崩溃
      • 饥人谷_芦花:- (void)testNoSelectorCrash {
        //测试1
        // id person1 = @"chenfanfang";
        // person1 = [person1 initWithName:@"cff" age:26 height:170 weight:110];

        //测试2
        Person *person2 = [Person new];
        [person2 performSelector:@selector(testCrash)];

        }
        使用测试2,会崩溃
        2017-08-30 09:50:24.346781+0800 AvoidCrashDemo[3981:946530] -[Person testCrash]: unrecognized selector sent to instance 0x17001a590
        2017-08-30 09:50:24.347024+0800 AvoidCrashDemo[3981:946530] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person testCrash]: unrecognized selector sent to instance 0x17001a590'
        *** First throw call stack:
        (0x18d602fe0 0x18c064538 0x18d609ef4 0x18d606f54 0x18d502d4c 0x100066e90 0x100066d8c 0x193734b04 0x1937346e0 0x19373af60 0x1937383fc 0x1937a9e68 0x1939b2430 0x1939b7e24 0x1939cc8b0 0x1939b50b8 0x18f1ac884 0x18f1ac6f0 0x18f1acaa0 0x18d5b142c 0x18d5b0d9c 0x18d5ae9a8 0x18d4deda4 0x19379efc8 0x193799c9c 0x100067270 0x18c4ed59c)
        libc++abi.dylib: terminating with uncaught exception of type NSException

        在使用按钮并且写了方法,但是方法没有实现的时候那个unrecognized selector sent to instance崩溃捕捉不了。可以解决吗?
      • DreamMmMmM:请问下如果引入这个框架后 对一些crash统计的SDK有影响吗
      • iii余光:看到你的GitHub上已经更新到2.2.3 pod上只更新到了1.6.7 什么更新呢
        还有一个项目线上遇到这个框架内部crash的问题 给你issue了 麻烦你看一下
        chenfanfang:@小怪兽饲养猿 用pod update 不要使用pod update --no-repo-update
        iii余光:@chenfanfang 感谢回复 恩恩 update 和search的最新都是1.6.7 后面对于unrecognized selector sent to instance 会集成到pod里来吗 还是需要一些测试
        chenfanfang:你好, 请用pod update ,会自动拉取最新版本,后面版本主要处理 “unrecognized selector sent to instance”。请问下,在app运行的时候,是否可以重现crash
      • 570c7a9752a8:能不能加一个拦截KVO(添加监听和移除监听)崩溃的,应该是多次移除或者添加监听引起的崩溃
      • iii余光:你好 请问是兼容iOS8+吗
        iii余光:@chenfanfang 我在项目中pod了你的框架 先感谢
        因为项目中总遇到NSString或者NSNumber[nil integerValue] crash 能不能把这个加上去呢
        chenfanfang:@小怪兽饲养猿 兼容的
      • 无限de想象:如果 .a 或者 .framework 内部崩溃呢 这个 可以规避过去吗?
        ame:NSRangeException
        reason:
        *** -[__NSArrayM objectAtIndex:]: index 1 beyond bounds [0 .. 0]
        callStackSymbols:
        0 CoreFoundation 0x000000018429aff8 <redacted> + 148
        1 libobjc.A.dylib 0x0000000182cfc538 objc_exception_throw + 56
        2 CoreFoundation 0x0000000184179200 <redacted> + 0
        3 SSP 0x0000000100220178 SSP + 1769848
        4 SSP 0x0000000100221428 SSP + 1774632
        5 SSP 0x00000001001bab74 SSP + 1354612
        6 SSP 0x00000001001ba64c SSP + 1353292
        7 UIKit 0x000000018a7111f8 <redacted> + 688
        8 UIKit 0x000000018a711410 <redacted> + 80
        9 UIKit 0x000000018a6feb14 <redacted> + 2152
        10 UIKit 0x000000018a716400 <redacted> + 120
        11 UIKit 0x000000018a4ae858 <redacted> + 176
        12 UIKit 0x000000018a3c907c <redacted> + 1200
        13 QuartzCore 0x00000001875b9274 <redacted> + 148
        14 QuartzCore 0x00000001875adde8 <redacted> + 292
        15 QuartzCore 0x00000001875adca8 <redacted> + 32
        chenfanfang:@无限de想象 可以的
      • Mister_H:大神写的真是不错,我有个问题,+ (void)becomeEffective是开始生效,如果想加一个结束生效要怎么写?
        chenfanfang:@Mister_H 你好,目前没有这个功能,由于最近都在做前端的东西,基本没什么更新,等有时间会将这个功能点添加进去
      • iOS开发那些事:楼主写的不错,学习了
      • 慕言的世界:请问dic[@"1"];这种快速取值走的是那个方法啊。我没拦截到
      • 怒煮西兰花:1.对象类型和方法不匹配 NSDictionary *dic = @"123"; dic[key];
        2.对象没有实现对应方法crash [self bb]; //bb方法只是去声明,没有实现

        以上两种情况会crash 并抛出这样的崩溃信息: [obj method] unrecognized selector sent to
        instance ....

        这样子好处理么,怎么处理
      • 倒骑毛驴看风月:uibutton 这个没有处理吗,
        即:-[ViewController xxx:]: unrecognized selector sent to instance 0x7f91f4e042f0
        在线等,急
        倒骑毛驴看风月:@chenfanfang 这类的什么时候可以加上,根据runtime这个是可以来的吧
        chenfanfang:@_Smile淰憶 你好,暂时还没有处理这类的crash
      • 菊上一枝梅:请问这个框架兼容iOS多少版本
      • 上个月:为什么不使用热更新? 还要第二天紧急再上线了一个版本
      • feca61443dfa:和现在项目里用的收集bug的bugtags冲突了。。不停地报错
      • 寂寞水蛙:你好,写的很不错,正是我需要的东西,但是有一个疑惑,把你这些代码封装起来做一个SDK.a的静态库,我试了一下,好像不能正常使用,想问一下是这样的吗?请大神看到尽快回复,谢谢!
        chenfanfang:有调用 becomeeffective吗?我周末试下
        寂寞水蛙:@chenfanfang 也没报错,只是我们写的异常崩溃了,然而AvoidCrash没起作用。大神可以帮忙试一下做成静态库吗?
        chenfanfang:你好,静态库我没试过,但原理上应该可以通过,你具体报什么错了?
      • macprofile:不要 @try @Catch ~ 内存会爆的~
        for ( 9999) {
        // coding
        }
        chenfanfang:@macRong 你好,若是捕获到异常,@try@Catch拦截到一小部分方法是会造成轻微的内存泄漏,若正常情况下不存在你所说的内存会爆,你可以进行测试看下
      • e2f2d779c022:写的很棒! 加油💪
      • HelloEverything:写的好!感谢你为大家带来这么好的文章.
        看了你的代码有一点不是太理解:获取崩溃信息
        -getMainCallStackSymbolMessageWithCallStackSymbols:callStackSymbols
        这个方法中对类名进行了是否以@")"结尾进行了判断.这个判断是干嘛的呢?
        chenfanfang:@HelloEverything 你好,这个判断是为了过滤分类(category),让定位更加准确

        比如有一个 NSString+Extension,里面有一个方法命叫 +(NSStirng *)getMainMessage的方法,这个方法中有数组越界的代码,那么如果不过滤的话,将会定位到NSString+Extension的getMainMessage的这个方法

        如果经过过滤,则会定位到外层的那个方法(也就是在哪里调用了getMainMessage)
        这样错误信息更有价值
        希望我的回答对你有所帮助
      • 会卷舌头的猫:获取堆栈主要崩溃信息的正则不够精确,类名和方法名之间,实例和方法名之间没有匹配空格,不过就截取堆栈信息的使用场景而言一般不会存在什么问题就是的
        chenfanfang:@会卷舌头的猫 :smile::smile:谢谢提醒,我倒是没注意到会有这样的情况
        会卷舌头的猫:@chenfanfang NSString *regularExpStr = @"[-\\+]\\[.+\\]"; 这一句的正则以下的情况也会匹配到
        +[NSObjectclass]
        chenfanfang:@会卷舌头的猫 你好,谢谢你的反馈。
        但是不存在你所说的问题:类名和方法名之间,实例和方法名之间没有匹配空格
        你有空的时候再看下,若有问题,请你留言,谢谢
      • 辉过来辉过去:这种捕获取怎么跟那些第三方的统计平台对接, 比如友盟统计分析, 苹果统计分析?
        chenfanfang:@辉过来辉过去 你好,友盟的文档我没去看过,但是腾讯的bugly有自定义异常,如下所示(猜测友盟应该也有)

        /**
        * 上报自定义Objective-C异常
        *
        * @param exception 异常信息
        */
        + (void)reportException:(NSException *)exception;

        /**
        * 上报错误
        *
        * @param error 错误信息
        */
        + (void)reportError:(NSError *)error;

        /**
        * @Brief 上报自定义错误
        *
        * @param category 类型(Cocoa=3,CSharp=4,JS=5,Lua=6)
        * @param aName 名称
        * @param aReason 错误原因
        * @param aStackArray 堆栈
        * @param info 附加数据
        * @param terminate 上报后是否退出应用进程
        */
        + (void)reportExceptionWithCategory:(NSUInteger)category name:(NSString *)aName reason:(NSString *)aReason callStack:(NSArray *)aStackArray extraInfo:(NSDictionary *)info terminateApp:(BOOL)terminate;
      • 子墨兮:objectsAtIndexes 会引起闪退
        哈哈西:不支持Swift文件的异常捕获
        chenfanfang:你好,这个框架暂时没有对这个方法进行捕获处理。你所说的崩溃属于系统的正常崩溃。下个版本把你所说的这个方法也添加进去
      • qBryant:这个不错。。。福音啊! :+1:
      • 鬼丶白:NSConcreteMutableAttributedString initWithString:: nil value 这个可以加一下吗
        chenfanfang:@soime 知道了 是[NSMutableAttributedString alloc] initWithString:<#(nonnull NSString *)#> 导致这样的崩溃信息
        chenfanfang:@soime 请问下,你是调用哪个方法才导致这样的崩溃?
        chenfanfang:@soime 好的,周末处理,这几天公司的事情比较多
      • changxiaobin:+ (void)exchangeClassMethod:(Class)anClass method1Sel:(SEL)method1Sel method2Sel:(SEL)method2Sel {
        Method method1 = class_getClassMethod(anClass, method1Sel);
        Method method2 = class_getClassMethod(anClass, method2Sel);
        //1.错误
        // method_setImplementation(method1, method_getImplementation(method2));
        // method_setImplementation(method2, method_getImplementation(method1));

        //2.正确
        IMP imp1 = method_getImplementation(method1);
        IMP imp2 = method_getImplementation(method2);
        method_setImplementation(method1, imp2);
        method_setImplementation(method2, imp1);
        //3.正确
        // method_exchangeImplementations(method1, method2);
        }

        在你的demo中修改这个方法: 方法1跟方法2有什么区别啊,为什么方法1 导致崩溃,方法2、3就能正常运行。
        chenfanfang:@changxiaobin 周末再慢慢研究,这几天公司比较忙
      • 鬼丶白: 0 0x00000001096cb34b __exceptionPreprocess + 171
        1 0x000000010912c21e objc_exception_throw + 48
        2 0x000000010972210e -[__NSArrayM setObject:atIndexedSubscript:] + 414
        3 0x0000000108b4e666 -[ViewController NSMutableArray_Test_SetObjectAtIndex] + 166
        4 0x0000000108b4e2f9 -[ViewController viewDidLoad] + 73
        5 0x0000000109c9006d -[UIViewController loadViewIfRequired] + 1258
        6 0x0000000109c904a0 -[UIViewController view] + 27
        7 0x0000000109b5a045 -[UIWindow addRootViewControllerViewIfPossible] + 71
        8 0x0000000109b5a796 -[UIWindow _setHidden:forced:] + 293
        9 0x0000000109b6e0a9 -[UIWindow makeKeyAndVisible] + 42
        10 0x0000000109ae7259 -[UIApplication _callInitializationDelegatesForMainScene:transitionContext:] + 4818
        11 0x0000000109aed3b9 -[UIApplication _runWithMainScene:transitionContext:completion:] + 1731
        12 0x0000000109aea539 -[UIApplication workspaceDidEndTransaction:] + 188
        13 0x000000010cc3676b __FBSSERIALQUEUE_IS_CALLING_OUT_TO_A_BLOCK__ + 24
        14 0x000000010cc365e4 -[FBSSerialQueue _performNext] + 189
        15 0x000000010cc3696d -[FBSSerialQueue _performNextFromRunLoopSource] + 45
        16 0x0000000109670311 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
        17 0x000000010965559c __CFRunLoopDoSources0 + 556
        18 0x0000000109654a86 __CFRunLoopRun + 918
        19 0x0000000109654494 CFRunLoopRunSpecific + 420
        20 0x0000000109ae8db6 -[UIApplication _run] + 434
        21 0x0000000109aeef34 UIApplicationMain + 159
        22 0x0000000108b4f6ef main + 111
        23 0x000000010c4a668d start + 1
      • chenfanfang:你好,嗯,这类问题,我已经有大概的了解(详情请看我与8楼的 Galen_Yang 的对话),是iOS 10系统有所更新才导致的。
        能麻烦你把控制台的输出日志发给我看下吗?我好修复这个BUG,谢谢


        我现在还没用Xcode8 所以一切在我这里运行都正常,在这里希望各位朋友能够多多得提出问题,谢谢了
        chenfanfang:@soime 对的,什么方法报错,就通过分类添加对应的方法,并且用runtime交换方法即可。
        鬼丶白:@chenfanfang
        //array set object at index
        [AvoidCrash exchangeInstanceMethod:arrayMClass method1Sel:@selector(setObject:atIndexedSubscript:) method2Sel:@selector(avoidCrashSetObject:atIndexedSubscript:)];
        这样可以解决不知道对不对
      • 鬼丶白:NSMutableArray_Test_SetObjectAtIndex 你好这个没有拦截到
      • lynsea:你好,比如我是KVC赋值的,本来我是用数组来接收的,但是后台传来null,在setValue那个方法就会崩溃,如何通过runtime来解决这一系列的问题呢,重写setValue那个方法吗.
        chenfanfang:@soime 嗯,有什么方法需要加入的,你也可以提出来,或者fork一份,添加完直接pull request我哈
        鬼丶白:@chenfanfang 希望赶紧加到框架中 :smirk:
        chenfanfang:@lynsea 你好,通过runtime可以解决这一系列问题。
        [self setValue:<#(nullable id)#> forKey:<#(nonnull NSString *)#>]
        [self setValue:<#(nullable id)#> forKeyPath:<#(nonnull NSString *)#>]
        [self setValue:<#(nullable id)#> forUndefinedKey:<#(nonnull NSString *)#>]
        [self setValuesForKeysWithDictionary:<#(nonnull NSDictionary<NSString *,id> *)#>]

        以上是所有KVC的方法,只要你建立一个NSObject的分类,按照这篇文章所讲,即可解决问题。AvoidCrash这个框架占时没有防止KVC崩溃的功能,这个周末有空,我更新下框架,就可以防止KVC的崩溃。 同时谢谢你的提醒,KVC也算比较常用,到时候加到框架中
      • inoryshu:你好!我下载了你的Demo发现运行“ NSArray_Test_ObjectAtIndex ”这个方法没有拦截异常,出现了崩溃,运行 “ NSMutableArray_Test_ObjectAtIndex ” 这个方法可以捕获异常!
        inoryshu:2016-10-11 10:31:07.461 AvoidCrashDemo[39269:1556851] *** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSSingleObjectArrayI objectAtIndex:]: index 3 beyond bounds [0 .. 0]'
        *** First throw call stack:
        (
        0 CoreFoundation 0x000000010404d34b __exceptionPreprocess + 171
        1 libobjc.A.dylib 0x0000000103aae21e objc_exception_throw + 48
        2 CoreFoundation 0x00000001040a5bdf -[__NSSingleObjectArrayI objectAtIndex:] + 111
        3 AvoidCrashDemo 0x00000001034d04ca -[ViewController NSArray_Test_ObjectAtIndex] + 122
        4 AvoidCrashDemo 0x00000001034d0369 -[ViewController viewDidLoad] + 73
        5 UIKit 0x000000010461206d -[UIViewController loadViewIfRequired] + 1258
        6 UIKit 0x00000001046124a0 -[UIViewController view] + 27
        7 UIKit 0
        inoryshu:@chenfanfang 我刚测试了一下如果把“__NSArrayI”改成 “__NSSingleObjectArrayI”就可以拦截
        chenfanfang:@Galen_Yang 你好,谢谢你反馈,但是我这里可以拦截崩溃。

        能麻烦把你崩溃的输出日志截图发给我吗,我排查下原因,谢谢
      • 可可_running:很棒!送上我一枚真诚的大赞!
      • LZM轮回:妹子好 写的不错
        chenfanfang:@LZM轮回 :joy::joy::joy::joy:你好,其实我是帅气的boy
      • 菠萝蜜菠萝炒饭:good boy :yum:
        chenfanfang:@菠萝蜜菠萝炒饭 其实我是女孩!:grin::grin::grin:信吗
      • 星兴:好东西~ :clap:
        chenfanfang:@星兴 谢谢
      • Dan_:不错的东西
        chenfanfang:@Dan_ 谢谢,若有不足之处,还望指出:blush:
      • 瞬csr:非常棒:+1:🏻
        chenfanfang:@瞬csr 谢谢,有不足之处,还请指出:blush:
        chenfanfang:@瞬csr 谢谢,有不足之处,还请指出:blush:

      本文标题:iOS runtime实用篇--和常见崩溃say good-by

      本文链接:https://www.haomeiwen.com/subject/nsaeyttx.html