美文网首页工作生活
iOS开发-简单分析防线上crash

iOS开发-简单分析防线上crash

作者: 妖精的尾巴毛 | 来源:发表于2019-07-04 15:38 被阅读0次

    我们开发APP,虽然在极力避免出现线上crash,但是某些情况还是没法把控,比如和后端约定好的数据格式,突然哪天给你换了,很容易导致crash。但是如果我们在任何地方都做防御性判断,代码会写得特别难受。

    之前看到有人开源了防止crash的代码,所以分析了下。这些方案主要利用runtime的方法交换和消息转发来实现,对那些容易引起crash的方法,添加判断,或者在crash之后走消息转发

    之前项目用到这个,NSObjectSafe就是这么一个开源库,只需要拖进工程就可以起作用。
    NSObjectSafe代码地址:github链接

    比如,向NSArray插入一个nil、获取NSArray长度之外的元素等等,从而导致APP奔溃。NSObjectSafe能有效避免这些奔溃。这些方案,基本都在load中交换这些容易引起crash的方法,并且添加判断,以此来避免异常。简化版就是下面这样:

    @implementation NSArray (Safe)
    + (void)load
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            /* 没内容类型是__NSArray0 */
            swizzleInstanceMethod(NSClassFromString(@"__NSArray0"),  @selector(objectAtIndex:),  @selector(hookObjectAtIndex:));
            /* 有内容obj类型才是__NSArrayI */
            swizzleInstanceMethod(NSClassFromString(@"__NSArrayI"),  @selector(objectAtIndex:),  @selector(hookObjectAtIndex:));
            .
            .//还有很多
            .
        });
    }
    
    - (id) hookObjectAtIndex:(NSUInteger)index {
        @synchronized (self) {
            if (index < self.count) {
                return [self hookObjectAtIndex:index];
            }
            SFAssert(NO, @"NSArray invalid index:[%@]", @(index));
            return nil;
        }
    }
    

    可以看到,这里面用了个+load 方法、runtime的方法交换。

    那么 +load 到底是如何被调用的呢?

    在 iOS 开发中,我们经常会使用 +load 方法来做一些在 main 函数之前的操作,比如方法交换(Method Swizzle)等。

    +load()方法的调用时机是这样的:
    1、当类或分类被添加到 Obj-C 运行时的时候被调用;可以实现该方法用来在加载时刻执行特定类的操作。
    2、动态加载和静态链接都能将 load 消息发送到类和分类,但前提是新加载类或分类实现了要响应的方法。
    3、初始化的顺序如下:
    链接的所有框架(Framework)中全部的构造器。
    镜像(Image)中所有的 +load 方法。
    镜像中所有的 C++ 静态构造器,以及 C/C++ 的 attribute(constructor) 函数。
    框架中链接的所有构造器。
    4、类的 +load 方法在其所有父类的 +load 方法调用之后调用。
    5、分类的 +load 方法在其主类的 +load 方法调用之后调用。

    了解到 +load 在运行时初始化加载镜像时就会被调用,使得可以有机会预先做很多事情。但正是因为其加载的时机非常靠前,如果在 +load 方法中做比较复杂且在主线程的操作,将会影响 App 启动时间,降低用户体验。所以APP启动优化可以尽量把放到这个方法的任务往后放,比如+(void)initialize中,他会在每个类初始化的时候调用一次。

    runtime会在运行时调用工程中的类的+load方法,并且不需要类在代码中被显示import,所以有些第三方可拖进工程就会起作用,而不需要显示import。比如:IQKeyboad、NSObjectSafe...
    从这里可以解开我之前的一个误解:一直以为垃圾代码(尤其是第三方库)留在工程里面不管他,以为它不会起作用,如果他里面实现了 +load 方法,还是可能会起作用的,一旦出bug了,那叫一个难找

    接着,看看runtime的方法交换:

    + (void)load{
        NSLog(@"UIViewController Hook load");
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Method oriMethod = class_getInstanceMethod([self class], NSSelectorFromString(@"dealloc"));
            Method repMethod = class_getInstanceMethod([self class], @selector(xx_dealloc));
            method_exchangeImplementations(oriMethod, repMethod);
        });
    }
    
    - (void)xx_dealloc{
        NSLog(@"%@ dealloc",NSStringFromClass([self class]));
    }
    

    或者像这样(根据系统版本修改字号):

    @implementation UIFont (hook)
    + (void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            [self swizzleClassMethod:@selector(systemFontOfSize:) withMethod:@selector(xx_systemFontOfSize:)];
        });
    }
    + (UIFont *)xx_systemFontOfSize:(CGFloat)fontSize{
        NSString *version = [UIDevice currentDevice].systemVersion;
        if (version.doubleValue >= 11.0) {
            return [UIFont xx_systemFontOfSize:25.0f];
        } else {
            return [UIFont xx_systemFontOfSize:14.0f];
        }
    }
    @end
    

    为什么xx_systemFontOfSize里面又调用了xx_systemFontOfSize?不会死循环么?
    嗯,这里不会,因为交换了方法的IMP,这里不细说。

    看NSObjectSafe源码,hook那么多不常见的类,为什么呢?比如__NSArray0、__NSArrayI、__NSSingleObjectArrayI等等。

    这些都是NSArray在某些情况下的实际类,NSArray在这里使用了类簇的模式。

    类簇是一种设计模式,它包含了一组私有的具体的类,这些类继承一个公开的抽象类,也即是基类,基类负责提供对外接口供调用者使用,具体类负责方法的真正实现, 我们只需要调用基类提供的接口来实现相关功能,而无需关心背后的具体实现细节。

    在Cocoa中,许多类实际上是以类簇的方式实现的,即它们是一群隐藏在通用接口之下的与实现相关的类。例如创建NSString对象时,实际上获得的可能是NSLiteralString、NSCFString、NSSimpleCString、NSBallOfString或者其他未写入文档的与实现相关的对象。

    NSNumber就是最常用的类簇实现的类之一。


    NSNumber类簇

    这种模式的好处就是,可以隐藏私有类,调用者只能使用基类暴露的API,而无需关心里面是什么类,以及背后的具体实现细节。即使将来添加更多的私有类,对外的基类API也没什么变化。

    技术分享记录~

    相关文章

      网友评论

        本文标题:iOS开发-简单分析防线上crash

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