AvoidCrash -- 远离常见的崩溃

作者: chenfanfang | 来源:发表于2016-11-29 21:54 被阅读3166次

    源码地址: AvoidCrash

    https://github.com/chenfanfang/AvoidCrash

    前言

    一个已经发布到AppStore上的App,最忌讳的就是崩溃问题。为什么在开发阶段或者测试阶段都不会崩溃,而发布到AppStore上就崩溃了呢?究其根源,最主要的原因就是数据的错乱。特别是 服务器返回数据的错乱,将严重影响到我们的App。


    Foundation框架存在许多潜在崩溃的危险

    • 将 nil 插入可变数组中会导致崩溃。
    • 数组越界会导致崩溃。
    • 根据key给字典某个元素重新赋值时,若key为 nil 会导致崩溃。
    • ......

    AvoidCrash简介

    • 这个框架利用runtime技术对一些常用并且容易导致崩溃的方法进行处理,可以有效的防止崩溃。
    • 并且打印出具体是哪个方法会导致崩溃,让你快速定位导致崩溃的代码。
    • 你可以获取到原本导致崩溃的主要信息<由于这个框架的存在,并不会崩溃>,进行相应的处理。比如:
      • 你可以将这些崩溃信息发送到自己服务器。
      • 你若集成了第三方崩溃日志收集的SDK,比如你用了腾讯的Bugly,你可以上报自定义异常。
    • 或许你会问有JSPatch就可以下发补丁来修复bug,为什么要用AvoidCrash?我只能说,AvoidCrash可以有效防止部分常见崩溃,JSPatch可以快速修复bug.推荐将两者都集成到项目中去。

    下面先来看下防止崩溃的效果吧

    可导致崩溃的代码

        NSString *nilStr = nil;
        NSArray *array = @[@"chenfanfang", nilStr];
    
    • 若没有AvoidCrash来防止崩溃,则会直接崩溃,如下图
    崩溃截图.png
    • 若有AvoidCrash来防止崩溃,则不会崩溃,并且会将原本会崩溃情况的详细信息打印出来,如下图
    防止崩溃的输出日志.png

    Installation【安装】

    From CocoaPods【使用CocoaPods】

    pod  AvoidCrash
    

    Manually【手动导入】

    • Drag all source files under floder AvoidCrash to your project.【将AvoidCrash文件夹中的所有源代码拽入项目中】
    • 对 NSMutableArray+AvoidCrash.m 文件进行 -fno-objc-arc 设置(若使用CocoaPods集成则无需手动配置),配置过程如下图:
    配置mutableArray.png

    使用方法

    • 在AppDelegate的didFinishLaunchingWithOptions方法中添加如下代码,让AvoidCrash生效
    //这句代码会让AvoidCrash生效,若没有如下代码,则AvoidCrash就不起作用
    [AvoidCrash becomeEffective];
    
       /*
        *  [AvoidCrash becomeEffective],是全局生效。若你只需要部分生效,你可以单个进行处理,比如:
        *  [NSArray avoidCrashExchangeMethod];
        *  [NSMutableArray avoidCrashExchangeMethod];
        *  .................
        *  .................
        */
    
    • 若你想要获取崩溃日志的所有详细信息,只需添加通知的监听,监听的通知名为:AvoidCrashNotification
    - (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(@"%@",note.userInfo);
    }
    
    • 下面通过打断点的形式来看下userInfo中的信息结构,看下包含了哪些信息
    userInfo信息结构.png
    • 再看下控制台输出日志来看下userInfo中的包含了哪些信息
    userInfo详细信息.png

    目前可以防止崩溃的方法有


    • NSArray
      • 1. NSArray的快速创建方式 NSArray *array = @[@"chenfanfang", @"AvoidCrash"]; //这种创建方式其实调用的是2中的方法

      • 2. +(instancetype)arrayWithObjects:(const id _Nonnull __unsafe_unretained *)objects count:(NSUInteger)cnt

      • 3. 通过下标获取元素 array[100]、[array objectAtIndex:100]

        • - (id)objectAtIndex:(NSUInteger)index
      • 4. - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes

      • 5. - (void)getObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range


    • NSMutableArray
      • 1. 通过下标获取元素 array[100]、[array objectAtIndex:100]
        • - (id)objectAtIndex:(NSUInteger)index
      • 2. - (void)setObject:(id)obj atIndexedSubscript:(NSUInteger)idx
      • 3. - (void)removeObjectAtIndex:(NSUInteger)index
      • 4. - (void)insertObject:(id)anObject atIndex:(NSUInteger)index
      • 5. - (NSArray *)objectsAtIndexes:(NSIndexSet *)indexes
      • 6. - (void)getObjects:(__unsafe_unretained id _Nonnull *)objects range:(NSRange)range

    • NSDictionary
      • 1. NSDictionary的快速创建方式 NSDictionary *dict = @{@"frameWork" : @"AvoidCrash"}; //这种创建方式其实调用的是2中的方法
      • 2. +(instancetype)dictionaryWithObjects:(const id _Nonnull __unsafe_unretained *)objects forKeys:(const id<NSCopying> _Nonnull __unsafe_unretained *)keys count:(NSUInteger)cnt

    • NSMutableDictionary
      • 1. - (void)setObject:(id)anObject forKey:(id<NSCopying>)aKey
      • 2. - (void)removeObjectForKey:(id)aKey

    • NSString
      • 1. - (unichar)characterAtIndex:(NSUInteger)index
      • 2. - (NSString *)substringFromIndex:(NSUInteger)from
      • 3. - (NSString *)substringToIndex:(NSUInteger)to {
      • 4. - (NSString *)substringWithRange:(NSRange)range {
      • 5. - (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement
      • 6. - (NSString *)stringByReplacingOccurrencesOfString:(NSString *)target withString:(NSString *)replacement options:(NSStringCompareOptions)options range:(NSRange)searchRange
      • 7. - (NSString *)stringByReplacingCharactersInRange:(NSRange)range withString:(NSString *)replacement

    • NSMutableString
      • 1. 由于NSMutableString是继承于NSString,所以这里和NSString有些同样的方法就不重复写了
      • 2. - (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString
      • 3. - (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc
      • 4. - (void)deleteCharactersInRange:(NSRange)range

    • KVC
      • 1.- (void)setValue:(id)value forKey:(NSString *)key
      • 2.- (void)setValue:(id)value forKeyPath:(NSString *)keyPath
      • 3.- (void)setValue:(id)value forUndefinedKey:(NSString *)key //这个方法一般用来重写,不会主动调用
      • 4.- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues

    • NSAttributedString
    • 1.- (instancetype)initWithString:(NSString *)str
    • 2.- (instancetype)initWithAttributedString:(NSAttributedString *)attrStr
    • 3.- (instancetype)initWithString:(NSString *)str attributes:(NSDictionary<NSString *,id> *)attrs

    • NSMutableAttributedString
    • 1.- (instancetype)initWithString:(NSString *)str
    • 2.- (instancetype)initWithString:(NSString *)str attributes:(NSDictionary<NSString *,id> *)attrs

    更新

    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

    期待

    • 如果你发现了哪些常用的Foundation中的方法存在潜在崩溃的危险,而这个框架中没有进行处理,希望你能 issue, 或者在简书私信我,我将这些方法也添加到AvoidCrash中。谢谢
    • 如果你在使用过程中遇到BUG,希望你能 issue, 或者在简书私信我。谢谢(或者尝试下载最新的框架代码看看BUG修复没有)
    • 毕竟一个人的能力有限,时间有限,希望有能力的你可以加入到这个项目中来,一起完善AvoidCrash。请Pull Requests我。

    About me -- 简书

    相关文章

      网友评论

      • butterflyer:arr[3]的这种数组越界没有hook主吧。。。最新版本是2.2.3吧。。
        butterflyer:pod 'AvoidCrash', '~> 2.3.0-beta'用的这个版本。。还是不行
      • 刘了个二:您好,想请教一下我用了NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithObject:@"name" forKey:nil];这个方法依然崩溃,你用runtime交换的方法没有走的
      • 骨气的牢笼:TaggedPointer 这么伪对象怎么做方法容错呢?跪求
      • macbookpro2100:Very Good ,Look like https://github.com/jasenhuang/NSObjectSafe Swizzle commonly used function of Foundation container to prevent nil crash
      • b78cd2ab90bb:在ios11上面貌似不能使用 这是什么原因
      • AllenYukin:通知增加了观察者 需要在appdeletage
        dalloc 方法中 移除么
      • 爱上程序元:ios11部分功能无效,*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM objectAtIndexedSubscript:]: index 17 beyond bounds for empty array',这个错误无法检测,还有就是还有就是uitableView的数据源故意加1,self.dataArr.count +1也无法检测,楼主可以看看嘛
        爱上程序元:貌似我找到问题了,iOS 11以前方法是[__NSArrayM objectAtIndex:],iOS 11以后方法是[__NSArrayM objectAtIndexedSubscript:],所以把NSMutableArray+AvoidCrash.m文件中的24行代码中的objectAtIndex方法替换成objectAtIndexedSubscript,我这边测试可以,版主可以自己看下,更新git上的数据。
        爱上程序元:问题原因好像是可变数组越界监测不到,不可变数组可以正常监测
        爱上程序元:这些错误以前测试都是可以监测到的
      • 不想重复造轮子:为什么要对 NSMutableArray+AvoidCrash.m 文件进行 -fno-objc-arc 设置
      • IUVO:你好,关于使用Bugly的上报异常,因为崩溃被避免了就不上报了,你说做相应处理即可,但是目前没有找到相关的方法,可以指点一下吗?
        chenfanfang:@IUVO bugly有上报自定义异常接口
      • 梵尘_341b: 总是炸在 @try{} 当将你面的代码注释掉就可使用 不会 崩溃 ,什么情况?
      • 文心简:在swift项目里没有效果??我试了不行 (swift和OC混编,swift为主)
      • 捏捏你的脸:runtime 高手 。
      • 饥人谷_芦花: //测试2
        Person *person2 = [Person new];
        [person2 performSelector:@selector(testCrash)];

        这个方法也会崩溃,当一个按钮加了方法,但是没有实现方法的时候,就会崩溃,请问能解决吗?
      • 饥人谷_芦花:- (void)viewDidLoad {
        [super viewDidLoad];

        UIButton*button=[UIButton buttonWithType:UIButtonTypeCustom];
        button.frame=CGRectMake(100, 100, 100, 50) ;
        button.backgroundColor=[UIColor yellowColor];
        [ button setTitle:@"test" forState:0];
        [button addTarget:self action:@selector(test:) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:button];




        }
        -(void)test:(UIButton*)button
        {
        NSLog(@"test");
        NSArray*array=@[@"1"];
        button.titleLabel.text=array;
        }
        2017-08-30 09:59:48.290713+0800 AvoidCrashDemo[3990:948104] -[__NSSingleObjectArrayI length]: unrecognized selector sent to instance 0x17000e040
        2017-08-30 09:59:48.291292+0800 AvoidCrashDemo[3990:948104] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSSingleObjectArrayI length]: unrecognized selector sent to instance 0x17000e040'
        *** First throw call stack:
        (0x18d602fe0 0x18c064538 0x18d609ef4 0x18d606f54 0x18d502d4c 0x193c2f808 0x19378971c 0x193731cc0 0x190922274 0x190916de8 0x190916ca8 0x19089234c 0x1908b93ac 0x1908b9e78 0x18d5b09a8 0x18d5ae630 0x18d5aea7c 0x18d4deda4 0x18ef49074 0x193799c9c 0x10003f238 0x18c4ed59c)
        libc++abi.dylib: terminating with uncaught exception of type NSException

        这种崩溃也防止不了啊,能解决吗?
        傍晚我陪着你:还有给我文本赋值数组的???
      • YY_Lee:建议把AvoidCrash类 .h文件引入的头文件放到.m文件中
      • 6ea54dc08ca1:Undefined symbols for architecture x86_64:
        "_OBJC_CLASS_$_AvoidCrash", referenced from:
        objc-class-ref in AppDelegate.o
        ld: symbol(s) not found for architecture x86_64
        clang: error: linker command failed with exit code 1 (use -v to see invocation)

        我这个刚到进入项目里 就报错了
      • 思考的快与慢:修复在键盘弹出状态下,按Home键进入后台会导致崩溃的bug。这段代码在哪体现的
      • 我的珊妮:日子能否添加奔溃所在的类名,所在行数
        i_MT:用腾讯的Bugly很好用,我一直在用
      • 那位小姐:我发现我的应用推到后台 然后在进入前台崩溃,定位不到
      • 码上翻身:您好!我发现一个问题,用于替换的方法,会一直在反复调用很多次。
      • 深度码农患者:你好,AvoidCrash 处理之后的崩溃会被 bugly 收集吗
      • iamjjh:赞
      • bigParis:键盘的问题怎么处理?
      • A409905186:1. 不建议全部采用try catch,逻辑判断为佳。
        2. 部分常用exception没有handle, 比如arrayByAddingObj, stringByAppendStr...
        y2015:@不留名的黄子嘉 oc的异常捕获机制就是形同虚设
        不留名的黄子嘉:@chenfanfang try的效率并不高吧。
        chenfanfang:@A409905186 捕获不到的再单独处理,@try @Catch 好处挺多,一个方法可能崩溃点有好几种情况,若自己手动处理要进行各种判断,并且还要自己写崩溃的原因,并且还破坏了系统的本来方法。

        用@try @Catch 可以捕获一个方法的所有崩溃点,可以获取到崩溃原因,无需手动写崩溃原因, 若不会导致崩溃,则走系统的原本方法,不会破坏原生性
      • Civel_Xu:如果不闪退了 问题还能排除吗
      • 不喜欢说话的小张同学:正是需要的,回去研究研究
      • xxttw::+1:🏻
      • CoderDancer:原理是不是运行时或者通过扩展处理了异常啊
      • newbiecoder:pod 导入 好像说找不到!任何造成的崩溃都会走这个通知方法吗?
        chenfanfang:@newbiecoder 你好,请您查看下是否是你的cocoapods太久没有更新了
      • 与伟大LEE同行:赞一个
        chenfanfang:@与伟大LEE同行 :谢谢
      • inoryshu:厉害:heart_eyes:
        chenfanfang:@Galen_Yang 谢谢,有不足之处还望指出

      本文标题:AvoidCrash -- 远离常见的崩溃

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