美文网首页
ios应用启动加载过程:类、分类加载

ios应用启动加载过程:类、分类加载

作者: 正_文 | 来源:发表于2020-07-06 15:34 被阅读0次

    一、类的加载

    上一篇我们有分析非懒加载类的加载过程,接下来我们可以在_read_images方法中打印一下,以验证是否加载了我们自己创建的类。

    // Realize non-lazy classes (for +load methods and static instances)
        for (EACH_HEADER) {
            classref_t *classlist = 
                _getObjc2NonlazyClassList(hi, &count);
            for (i = 0; i < count; i++) {
                Class cls = remapClass(classlist[i]);
                printf("_getObjc2NonlazyClassList Class:%s\n",cls->mangledName());
                if (!cls) continue;
    

    通过打印结果你会发现,所有实现了+load方法的类都被正常打印,没有实现这个方法的则没有打印。

    懒加载类和非懒加载类有什么区别?
    看上面代码注释:Realize non-lazy classes (for +load methods and static instances)

    1.1 懒加载类的加载

    那么懒加载类是什么时候加载的?我们可以调用调用一下Dog类alloc方法

    alloc.png

    然后进入跟入到lookUpImpOrForward方法(这是一个很重要的方法),alloc是一个类方法,那此时cls就应该是Dog的元类inst就是真正的对象。

    lookUpImpOrForward.png

    但是我们通过LLDB发现,这个inst还只是一个地址,说明Dog还没有被初始化。

    ro&rw.png
    打断点然后跟进去,最终我们会发现一个我们上一篇分析过的方法realizeClassWithoutSwift,这里会进行ro/rwcategory的操作。
    这时候打印inst(即Dog对象),可以看到rw有值了,说明类已经被加载。

    1.2 总结

    下面总结一下懒加载类加载的流程:

    1. 类第一次加载的时候没有缓存,所以会来到_class_lookupMethodAndLoadCache3 -> lookUpImpOrForward
    2. lookUpImpOrForward会做一次[图片上传中...(懒加载类.png-c5360e-1593754564326-0)]
      判断,如果没有实现(!cls->isRealized()),会来到realizeClassWithoutSwift,也就是最终实现类加载的地方。

    懒加载类非懒加载类的调用堆栈如下:

    懒加载类.png
    非懒加载类.png

    二、分类的加载

    2.1 分类的底层实现

    为了探究分类的底层实现,我们先新建一个分类:

    @interface Dog (test)
    
    @property (nonatomic,strong) NSString *name;
    - (void)sayHello;
    + (void)sayByebye;
    
    @end
    

    clang命令重写一下:

    clang -rewrite-objc Dog+test.m -o category.cpp
    

    在文件末尾,我们可以看到底层方法刚好对应了分类代码:

    static struct _category_t _OBJC_$_CATEGORY_Dog_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
    {
        "Dog",
        0, // &OBJC_CLASS_$_Dog,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Dog_$_test,
        (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Dog_$_test,
        0,
        (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Dog_$_test,
    };
    static void OBJC_CATEGORY_SETUP_$_Dog_$_test(void ) {
        _OBJC_$_CATEGORY_Dog_$_test.cls = &OBJC_CLASS_$_Dog;
    }
    

    为了看到分类相对完整的底层实现,建议给新建的分类添加实例方法、类方法、属性。

    _OBJC_$_CATEGORY_INSTANCE_METHODS_Dog_$_test:实例方法
    _OBJC_$_CATEGORY_CLASS_METHODS_Dog_$_test:类方法
    _OBJC_$_PROP_LIST_Dog_$_test:属性
    同时我们还看到如下代码:

    static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
        &_OBJC_$_CATEGORY_Dog_$_test,
    };
    

    这表明分类存储在__DATA段的__objc_catlist section里面。

    2.2 分类的定义

    struct category_t {
        const char *name;
        classref_t cls;
        struct method_list_t *instanceMethods;
        struct method_list_t *classMethods;
        struct protocol_list_t *protocols;
        struct property_list_t *instanceProperties;
        // Fields below this point are not always present on disk.
        struct property_list_t *_classProperties;
    
        method_list_t *methodsForMeta(bool isMeta) {
            if (isMeta) return classMethods;
            else return instanceMethods;
        }
    
        property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    };
    

    name : 是分类所关联的类,也就是类的名字,而不是分类的名字
    cls : 我们在前面可以看到 clang 重写后这个值为 0,但是后面有注释为 &OBJC_CLASS_$_Dog,也就是我们的类对象的定义,所以这里其实就是我们要扩展的类对象,只是在编译期这个值并不存在
    instanceMethods : 分类上存储的实例方法
    classMethods :分类上存储的类方法
    protocols :分类所实现的协议
    instanceProperties :分类所定义的实例属性,不过我们一般在分类中添加属性都是通过关联对象来实现的
    _classProperties :分类所定义的类属性。这里有一行注释:Fields below this point are not always present on disk. 下面的内容并不是一直在磁盘上保存,也就是说他是一个私有属性,但并不是一直都存在的。

    三、分类的加载

    前面我们已经知道了懒加载类和非懒加载类,分类也分为懒加载和非懒加载。

    3.1 懒加载类 & 懒加载分类

    从前面的分析,我们知道分类的加载过程:realizeClassWithoutSwift -> methodizeClass -> methodizeClass -> attachCategories

    attachCategories.png
    通过断点,我们发现catsNULL,即unattachedCategoriesForClass并没有获取到分类。但是我们打印rorw,发现我们methods是有内容的(sayByebye是我们手动添加到分类的类方法)。
    也就是说,如果是懒加载类,并且分类也是懒加载,那么分类在编译时直接加载到了类的 ro 里面,然后在运行时被拷贝到了类的 rw 里面。

    3.2 总结

    分类的加载可以简单以是否实现load方法分类:

    1. 懒加载:编译时确定
    2. 非懒加载:运行时确定

    这也说明分类的加载是不一样的,我们可以把他们分为四类:

    1. 懒加载分类 & 懒加载类
      类的加载在第一次消息发送的时候,而分类的加载则在编译时
    消息发送的时候 -> lookuporforward ->  realizeClassWithoutSwift -> methodlizeClass 
    不进addUnattachedCategoryForClass,直接走data()
    
    1. 懒加载分类 & 非懒加载类
      类的加载在 _read_images 处,分类的加载还是在编译时
    read_images -> realizeClassWithoutSwift -> methodlizeClass -> 不需要添加表 -> 直接在相应data() -> ro
    
    1. 非懒加载分类 & 懒加载类
      类的加载在 load_images 内部,分类的加载在类加载之后的 methodizeClass
    3.1 发送消息的时候就去读取 -> realizeClassWithoutSwift -> methodlizeClass
    3.2 就是我的类要在消息发送的时候才有 - 但是我的分类提前了 - 需要加载 - read_images - addUnattachedCategoryForClass - 但是没有实现类 就会在下面 prepare_load_methods 实现
    3.3 prepare_load_methods - realizeClassWithoutSwift 给你提前了实现类的信息 - unattachedCategoriesForClass
    
    1. 非懒加载分类 & 非懒加载类
      类的加载在 _read_images 处,分类的加载在类加载之后的 reMethodizeClass
    read_images -> realizeClassWithoutSwift -> methodlizeClass -> addUnattachedCategoryForClass -> 判断是否实现 -> 这里看到上面一行就在read_images 实现了
    if (cls->isRealized()) {
       remethodizeClass(cls); -> 实现类信息
    }
    attachCategories 加载分类数据进来
    
    参考资料

    sunnyxx:objc_category_secret
    美团:category

    相关文章

      网友评论

          本文标题:ios应用启动加载过程:类、分类加载

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