美文网首页
+load方法原理解析

+load方法原理解析

作者: 85ca4232089b | 来源:发表于2020-03-16 14:50 被阅读0次

objc源码下载地址
寻找类的load方法不是走的消息发送机制,故不会出现消息覆盖现象

  • (void)load
void _objc_init(void)
{
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

调用load_images

load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!hasLoadMethods((const headerType *)mh)) return;
    recursive_mutex_locker_t lock(loadMethodLock);
//互斥递归锁 Class,来解决多次上锁而不会发生死锁的问题,其作用与 NSRecursiveLock 相同,但不是由 NSLock 再封装,而是通过 C 为 Runtime 的使用场景而写的一个 Class。
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }
    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

在所有的images映射到内存之后,开始加载load方法

  1. prepare_load_methods:准备调用(类的load 方法和分类的load方法两种)
void prepare_load_methods(const headerType *mhdr)
{
   size_t count, i;

   runtimeLock.assertLocked();
//拿到所有的类信息(类信息不止是在类当中有,分类当中也会有load方法)
   classref_t *classlist = 
       _getObjc2NonlazyClassList(mhdr, &count);
   for (i = 0; i < count; i++) {
//类当中的load方法
       schedule_class_load(remapClass(classlist[i]));// 通过 remapClass 获取类指针
   }
//取出所有分类当中的load方法
   category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
}

1.1schedule_class_load:类的load 方法
如果一个类实现了 +load 方法,那么它就是个 NonlazyClass
1.1.1 拿到所有加载进去的类的列表
classref_t *classlist = _getObjc2NonlazyClassList(mhdr, &count);
1.1.2. 开始遍历 schedule_class_load

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize
    if (cls->data()->flags & RW_LOADED) return;
//    递归到深层(超类)运行,总是能够保证没有调用 load 方法的父类先于子类加入 loadable_classes 数组,从而确保其调用顺序的正确性。
    schedule_class_load(cls->superclass);//superclass父类
// 将需要执行 load 的 Class 添加到一个全局列表中
    add_class_to_loadable_list(cls);
 // 标记 RW_LOADED 符号
    cls->setInfo(RW_LOADED); 
}

1.1.3. schedule_class_load(cls->superclass);递归的方式调用父类的load方法
1.1.4. add_class_to_loadable_list(cls);把这个类的load方法驾到list中
1.2 category的方法的准备
_getObjc2NonlazyClassList:获取所有拥有 load 方法的类(class)
category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);

从 section 中获取类列表,并对每个 class 进行 remap 以及 realize 处理
 category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        // 通过 remapClass 获取 Category 对象存有的 Class 对象
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
// 对类进行第一次初始化,主要用来分配可读写数据空间并返回真正的类结构
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
// 将需要执行 load 的 Category 添加到一个全局列表中
        add_category_to_loadable_list(cat);
    }

_getObjc2NonlazyCategoryList:获取所有拥有 load 方法的 分类(category)
remapClass:通过 remapClass 获取类指针
none-lazy class 是一个很特殊的区中取出来的 class 列表,只有 load 方法实现的类中才提前设置它的一些属性,否则,只加载最基本的数据即可。

realizeClass:如果类中没有 +load 方法,那么 realize() 返回的就是 false,否则为空。并且另外的一些相关属性也会有所变化,比如: hasCxxCtor & hasCustomAWZ
add_category_to_loadable_list:放入到静态变量数组 以及 loadable_categories

1.3call_load_methods:调用

void call_load_methods(void)
{
    static bool loading = NO;// 是否已经录入
    bool more_categories;// 是否有关联的 Category

    loadMethodLock.assertLocked();

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return; // 由于 loading 是全局静态布尔量,如果已经录入方法则直接退出
    loading = YES;
    
    // 声明一个 autoreleasePool 对象
    // 使用 push 操作其目的是为了创建一个新的 autoreleasePool 对象
    void *pool = objc_autoreleasePoolPush();

    do {
        // 1. Repeatedly call class +loads until there aren't any more,重复调用 load 方法,直到没有
        while (loadable_classes_used > 0) {
            call_class_loads();
        }

        // 2. Call category +loads ONCE,调用 Category 中的 load 方法,这个只要调用一次,毕竟category没有父类这东西
        //这里可以在一定程度上确保类的 load 方法会先于分类调用。
        //但是这里不能完全保证调用顺序的正确。如果分类的镜像在类的镜像之前加载到运行时,这里的代码就没法保证顺序的正确了
        more_categories = call_category_loads();

        // 3. Run more +loads if there are classes OR more untried categories,继续调用,直到所有 Class 全部完成
    } while (loadable_classes_used > 0  ||  more_categories);
    
    // 将创建的 autoreleasePool 对象释放
    objc_autoreleasePoolPop(pool);
    // 更改全局标记,表示已经录入
    loading = NO;
}

do while 分为两步:
class的load调用:call_class_loads();->static void call_class_loads(void)
categories的调用: more_categories = call_category_loads();->static bool call_category_loads(void)

        load_method_t load_method = (load_method_t)cats[i].method;
        Class cls;
        if (!cat) continue;
        cls = _category_getClass(cat);
        if (cls  &&  cls->isLoadable()) {
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s(%s) load]\n", 
                             cls->nameForLogging(), 
                             _category_getName(cat));
            }
            (*load_method)(cls, SEL_load);
            cats[i].cat = nil;
        }

拿到method。直接函数指针调用函数 (*load_method)(cls, SEL_load);
因为系统执行load方法时不是走消息发送机制,故不存在分类load方法覆盖类load方法的现象,而代码手动调用类的load方法,因为是走消息发送机制,分类load方法会优先执行.
总结一下

load 方法的调用情况至此已经全部清晰。思路梳理如下三大流程:

  1. Load Images: 通过 dyld 载入 image 文件,引入 Class。
  2. Prepare Load Methods: 准备 load 方法。过滤无效类、无效方法,将 load 方法指针和所属 Class 指针收集至全局 Class 存储线性表 loadable_classes 中,其中会涉及到自动扩展空间和父类优先的递归调用问题。
  3. Call Load Methods: 根据收集到的函数指针,对 load 方法进行动态调用。进一步过滤无效方法,并记录 log 日志。
    load的调用特点:
    • 当类被导入到项目的时候就会执行load函数,
    • 在main函数开始执行之前的,这个类是否被用到无关
    • 每个类的load函数只会自动调用一次
    • load函数是系统自动加载的,不需要调用父类的load函数
    • 父类和子类都实现load函数时,父类的load方法执行顺序要优先于子类。
    • 子类未实现load方法时,不会调用父类load方法
    • 类中的load方法执行顺序要优先于类别(Category)
    •有多个类别(Category)都实现了load方法,这几个load方法都会执行,但执行顺序不确定(其执行顺序与类别在Compile Sources中出现的顺序一致)

initialize方法解析

相关文章

网友评论

      本文标题:+load方法原理解析

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