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

2020-07-06  本文已影响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

上一篇 下一篇

猜你喜欢

热点阅读