Objective-C中+load方法的调用历程

根类NSObject中有两个类方法用于加载和初始化类:

+ (void)load;
+ (void)initialize;
区别 +load +initialize
调用时机 在类被加载之后,调用+initialize之前调用 用于加载该类之后初次使用该类之前调用
实现环境 可以由类实现,也可以在分类实现 不能在分类重写
实现处代码 当类被加载时,该处实现代码被调用一次 当类收到第一条消息时,+initialize会被调用一次;没有收到消息就不会被调用

1、分析objc-loadmethod.m文件

关于+load方法的大部分操作,Runtime 封装在文件objc-loadmethod.m中。想要了解+load方法的调用流程,首先需要了解一下关于+load方法的功能函数!!

1.1、该文件中的变量
1.1.1、存储实现+load方法类的数组
struct loadable_class {
    Class cls; 
    IMP method;
};

static struct loadable_class *loadable_classes = nil;
static int loadable_classes_used = 0;
static int loadable_classes_allocated = 0;
  • 结构体loadable_class用于存储实现了 +load 方法的Class+load 方法的实现IMP
  • 结构数组loadable_classes :存储结构loadable_class
  • 静态变量 loadable_classes_used 用于记录 add_class_to_loadable_list() 函数的调用次数,同时也是 loadable_classes 数组的元素个数;
1.1.2、存储实现+load方法分类的数组
//存储了 +load 方法所属的 Category 和 +load 方法的IMP
struct loadable_category {
    Category cat;  //+load 方法所属的 Category;可能为 nil
    IMP method;
};

static struct loadable_category *loadable_categories = nil;
static int loadable_categories_used = 0;
static int loadable_categories_allocated = 0;
  • 结构体loadable_category用于存储实现了 +load 方法的category+load 方法的实现IMP
  • 结构数组loadable_categories :存储元素为 loadable_category
  • 静态变量loadable_categories_used 用于记录 add_category_to_loadable_list()函数的调用次数,也是 loadable_categories 数组的元素个数;
1.2、结构数组 loadable_classes 的增删操作
1.2.1、向数组 loadable_classes 添加一个类
void add_class_to_loadable_list(Class cls){
    IMP method;
    loadMethodLock.assertLocked();
    method = cls->getLoadMethod();//获取类 cls 的 +load 方法的 IMP
    if (!method) return; 
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        //如果数组 loadable_classes 分配的内存已用完,对数组进行动态扩容
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)realloc(loadable_classes,
                              loadable_classes_allocated *sizeof(struct loadable_class));
    }
    
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}

该函数的主要的功能就是将一个实现了 +load 的指定类添加到数组 loadable_classes中:

  • 如果该类没有实现+load 方法,自然是什么操作也不做;
  • 如果如果数组 loadable_classes 分配的内存已用完,对数组进行动态扩容;
  • 将指定的类与IMP存储在数组 loadable_classes中;
  • loadable_classes_used数组加 1,用于记录该函数的调用次数;相当于数组loadable_classes 的元素个数;
1.2.2、移除数组 loadable_classes中某个类
void remove_class_from_loadable_list(Class cls){
    loadMethodLock.assertLocked();
    if (loadable_classes) {
        int i;
        //遍历结构数组 loadable_classes,根据入参 cls 找到数组中指定的元素
        for (i = 0; i < loadable_classes_used; i++) {
            if (loadable_classes[i].cls == cls) {
                loadable_classes[i].cls = nil;
                if (PrintLoading) {
                    _objc_inform("LOAD: class '%s' unscheduled for +load", cls->nameForLogging());
                }
                return;
            }
        }
    }
}

该函数的主要功能就是遍历数组loadable_classes ,找到指定的类,并将该类从数组loadable_classes中移除。

1.3、结构数组 loadable_categories 的增删操作
1.3.1、向数组 loadable_categories 添加一个分类
void add_category_to_loadable_list(Category cat){
    IMP method;
    loadMethodLock.assertLocked();
    method = _category_getLoadMethod(cat);
    if (!method) return;
    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
       //如果数组 loadable_categories 分配的内存已用完,对数组进行动态扩容
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

该函数的主要的功能就是将一个实现了 +load 的指定分类添加到数组 loadable_categories中:

  • 如果该分类没有实现+load 方法,自然是什么操作也不做;
  • 如果数组 loadable_categories 分配的内存已用完,对数组进行动态扩容;
  • 将指定的分类与IMP存储在数组 loadable_categories中;
  • loadable_categories_used数组加 1,用于记录该函数的调用次数;相当于数组loadable_categories 的元素个数;
1.3.2、移除数组 loadable_categories中某个分类
void remove_category_from_loadable_list(Category cat){
    loadMethodLock.assertLocked();
    if (loadable_categories) {
        int i;
        //遍历结构数组 loadable_categories,根据入参 cat 找到数组中指定的元素
        for (i = 0; i < loadable_categories_used; i++) {
            if (loadable_categories[i].cat == cat) {
                loadable_categories[i].cat = nil;
                if (PrintLoading) {
                    _objc_inform("LOAD: category '%s(%s)' unscheduled for +load",_category_getClassName(cat),  _category_getName(cat));
                }
                return;
            }
        }
    }
}

该函数的主要功能就是遍历数组 loadable_categories ,找到指定的分类,并将该分类从数组 loadable_categories中移除。

1.4、使用数组 loadable_categoriesloadable_classes

objc-loadmethod.m文件的 call_load_methods()函数,是调用程序中实现的 +load 方法的入口:

void call_load_methods(void){
    static bool loading = NO;
    bool more_categories;
    loadMethodLock.assertLocked();
    if (loading) return;
    loading = YES;

    void *pool = objc_autoreleasePoolPush();
    do {
        // 1. 重复调用 loadable_classes 数组上的 +load,直到不再有其他类为止
        while (loadable_classes_used > 0) {
            //遍历数组 loadable_classes ,调用数组中所有挂起的类的 +load 方法; 调用之后,将 loadable_classes_used 置为 0;
            call_class_loads();
        }

        // 2. 调用 loadable_categories 数组中的 +load 一次:调用该函数期间,是否有新的分类添加到数组 
        more_categories = call_category_loads();

    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);
    loading = NO;
}

该函数的主要功能就是:

  • 调用 call_class_loads() 函数,执行数组loadable_classes中的 +load方法;
  • 调用 call_category_loads() 函数,执行数组loadable_categories中的 +load方法;
1.4.1、调用数组loadable_classes中的 +load方法
static void call_class_loads(void){
    int i;
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    for (i = 0; i < used; i++) {
        Class cls = classes[i].cls;
        load_method_t load_method = (load_method_t)classes[i].method;
        if (!cls) continue;  //如果该类为 nil ,则直接返回
        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
        }
        (*load_method)(cls, SEL_load);//通过函数指针执行指定类 cls 的 +load 方法
    }
    if (classes) free(classes);//释放列表
}

该函数的主要功能就是:

  • 遍历数组loadable_classes_used,取出每个元素的类与 +load的实现,然后执行该实现 (*load_method)(cls, SEL_load)
  • 遍历数组loadable_classes_used完毕,释放该数组,重置计数loadable_classes_used
1.4.2、调用数组loadable_categories中的 +load方法
static bool call_category_loads(void){
    int i, shift;
    bool new_categories_added = NO;//该函数返回值
    
    struct loadable_category *cats = loadable_categories;
    int used = loadable_categories_used;
    int allocated = loadable_categories_allocated;
    loadable_categories = nil;
    loadable_categories_allocated = 0;
    loadable_categories_used = 0;
    for (i = 0; i < used; i++) {
        Category cat = cats[i].cat;
        load_method_t load_method = (load_method_t)cats[i].method;//获取分类 +load 方法的 IMP
        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);//通过函数指针执行指定分类的 +load 方法
            cats[i].cat = nil;
        }
    }

    //将所有调用 +load 方法的分类移除数组
    shift = 0;
    for (i = 0; i < used; i++) {
        if (cats[i].cat) {
            cats[i-shift] = cats[i];
        } else {
            shift++;
        }
    }
    used -= shift;

    //将新添加到数组loadable_categories的分类存储到数组 cats 上
    new_categories_added = (loadable_categories_used > 0);//是否有新的分类添加到数组 loadable_categories
    for (i = 0; i < loadable_categories_used; i++) {
        if (used == allocated) {
            allocated = allocated*2 + 16;
            cats = (struct loadable_category *)realloc(cats, allocated *sizeof(struct loadable_category));
        }
        cats[used++] = loadable_categories[i];
    }
    if (loadable_categories) free(loadable_categories);//释放数组 loadable_categories

    if (used) {
        loadable_categories = cats;
        loadable_categories_used = used;
        loadable_categories_allocated = allocated;
    } else {//如果没有要加载的分类,则销毁列表。
        if (cats) free(cats);
        loadable_categories = nil;
        loadable_categories_used = 0;
        loadable_categories_allocated = 0;
    }
    if (PrintLoading) {
        if (loadable_categories_used != 0) {
            _objc_inform("LOAD: %d categories still waiting for +load\n",loadable_categories_used);
        }
    }
    return new_categories_added;
}

该函数的主要功能就是:

  • 遍历数组loadable_categories,取出每个元素的分类与 +load的实现,然后执行该实现 (*load_method)(cls, SEL_load)
  • 将所有调用 +load 方法的分类移除数loadable_classes_used
  • 接着判断是否有新的分类被添加到数组loadable_categories;如果有,则返回 YES
1.5、文件objc-loadmethod.m功能总结
1.5.1、全局变量
  • 结构数组loadable_classes 中的每个元素都存储了类以及它的 +load 方法的 IMP
  • 结构数组 loadable_categories中的每个元素都存储了分类以及它的 +load 方法的 IMP;
1.5.2、功能函数
  • add_class_to_loadable_list() 函数将一个实现了 +load 方法的类添加到数组 loadable_classes
  • add_category_to_loadable_list()函数将一个实现了 +load 方法的分类添加到数组 loadable_categories
  • remove_class_from_loadable_list() 函数从数组 loadable_classes 中移除指定类;
  • remove_category_from_loadable_list() 函数从数组 loadable_categories 中移除指定分类;
  • call_class_loads() 函数遍历数组 loadable_classes 调用数组中所有挂起的类的 +load 方法;调用之后数组 loadable_classes 置为 nil
  • call_category_loads() 函数遍历数组 loadable_categories调用分类挂起的 +load 方法;
  • call_load_methods() 函数调用所有挂起(未注册)的类和分类的 +load 方法。

2、+load方法的调用历程

objc-loadmethod.m 文件中的变量与函数来看:程序中类或者分类实现的+load方法,Runtime 都记录在了数组loadable_classes 与数组 loadable_categories,那么何时调用指定类或者分类的+load方法呢? 也就是说何时使用这两个数组的元素呢?

首先打印PeopleModel类的+load方法的调用栈信息:

(lldb) thread backtrace
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
  * frame #0: 0x0000000100000bd7 objc-test`+[PeopleModel load](self=PeopleModel, _cmd="load") at PeopleModel.m:12
    frame #1: 0x000000010037218c libobjc.A.dylib`call_class_loads() at objc-loadmethod.mm:200
    frame #2: 0x0000000100359b0d libobjc.A.dylib`::call_load_methods() at objc-loadmethod.mm:326
    frame #3: 0x000000010035996a libobjc.A.dylib`::load_images(path="/Users/longlong/Library/Developer/Xcode/DerivedData/SourceCode-gbviaxaumpwhkieshjwwgrxrxpva/Build/Products/Debug/objc-test", mh=0x0000000100000000) at objc-runtime-new.mm:2035
    frame #4: 0x000000010000a454 dyld`dyld::notifySingle(dyld_image_states, ImageLoader const*, ImageLoader::InitializerTimingList*) + 420
    frame #5: 0x000000010001c0c5 dyld`ImageLoader::recursiveInitialization(ImageLoader::LinkContext const&, unsigned int, char const*, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 337
    frame #6: 0x000000010001b254 dyld`ImageLoader::processInitializers(ImageLoader::LinkContext const&, unsigned int, ImageLoader::InitializerTimingList&, ImageLoader::UninitedUpwards&) + 134
    frame #7: 0x000000010001b2e8 dyld`ImageLoader::runInitializers(ImageLoader::LinkContext const&, ImageLoader::InitializerTimingList&) + 74
    frame #8: 0x000000010000a774 dyld`dyld::initializeMainExecutable() + 199
    frame #9: 0x000000010000f78f dyld`dyld::_main(macho_header const*, unsigned long, int, char const**, char const**, char const**, unsigned long*) + 6237
    frame #10: 0x00000001000094f6 dyld`dyldbootstrap::start(macho_header const*, int, char const**, long, macho_header const*, unsigned long*) + 1154
    frame #11: 0x0000000100009036 dyld`_dyld_start + 54

为了优化程序启动速度和利用动态库缓存,iOS 系统采用了共享缓存技术;dyld 缓存在 iOS系统中。

dyld 从 dyldStartup.s 文件开始执行:

  • 其中用汇编实现的 __dyld_start 函数里面调用了 dyldbootstrap::start() 函数;
  • 然后调用了 dyld::_main() 函数,创建镜像加载类 ImageLoader,分析 Mach-O 文件;
  • 接着根据指定的 Mach-O 文件的头信息mach_header 调用load_images()函数加载镜像信息

针对 dyld 的调用流程,这里不做过多分析!笔者从 Runtime 库中objc-runtime-new.mm文件的load_images()函数说起:

void load_images(const char *path __unused, const struct mach_header *mh){
    if (!hasLoadMethods((const headerType *)mh)) return;
    recursive_mutex_locker_t lock(loadMethodLock);
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }
    call_load_methods();
}

通过该函数的入参可知,该函数可能会被多次调用,每次调用,处理一个指定 mach_header 中的 +load 信息。该函数的全部代码如上所示,执行的功能很简单:

  • 1、判断该 Mach-O 文件是否有实现的 +load 方法,如果没有就直接返回;否则向下执行;
  • 2、调用 prepare_load_methods() 函数将该 Mach-O 文件中所有实现的 +load 方法分别存储到数组loadable_classes 与数组 loadable_categories
  • 3、调用 call_load_methods() 函数执行数组loadable_classes 与数组 loadable_categories+load 方法;

关于call_load_methods() 函数的功能,笔者在章节 1已经详细讲过,这里不再重复!我们来看看 prepare_load_methods() 函数:

2.1、prepare_load_methods()函数
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++) {
        schedule_class_load(remapClass(classlist[i]));
    }
    
    category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        Class cls = remapClass(cat->cls);
        if (!cls) continue;  // category for ignored weak-linked class
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);
    }
}

分析该函数,主要功能为:

  • 1、获取程序中所有实现了 +load 方法的类列表 classlist 与分类列表 categorylist
  • 2、遍历类列表 classlist ,通过调用schedule_class_load()函数,将类与它的+load 方法存储到数组loadable_classes 中;
  • 3、遍历分类列表 categorylist ,通过调用add_category_to_loadable_list()函数,将分类与它的+load 方法存储到数组loadable_categories 中;
2.1.1、schedule_class_load()函数
static void schedule_class_load(Class cls){
    if (!cls) return;
    assert(cls->isRealized());  // _read_images should realize
    
    // 已经添加 Class 的 +load 方法到调用列表中
    if (cls->data()->flags & RW_LOADED) return;
    
    // 确保 super 已经被添加到 +load 列表中,默认是整个继承者链的顺序
    schedule_class_load(cls->superclass);
    
    // 将IMP和Class添加到调用列表
    add_class_to_loadable_list(cls);
    
    // 设置Class的flags,表示已经添加Class到调用列表中
    cls->setInfo(RW_LOADED);
}

在该函数中,看到了 add_class_to_loadable_list() 函数将类与它的+load 方法存储到数组loadable_classes 中。
在存入数组loadable_classes之前,必须确保它的父类如果实现+load 方法则在它之前加入数组loadable_classes

2.2、调用时机

众所周知,程序通过入口函数 main() 开始执行,而 +load 的执行,却是在 Runtime 中实现的,此时程序并没有走到入口函数 main()

+load 方法可能被加载多次嘛?
如何确保+load 方法只被加载一次?

总结:

  • +load 方法是 Runtime 调用的,无需开发者去调用;
  • +load 的调用顺序遵循:父类调用+load之后,子类才能调用;
  • +load 方法是在所有类被 Runtime 写入内存后才调用的;
  • 子类的+load方法是按照 Compile Sources 的排列顺序加载的,但要遵循调用子类加载之前,必须先调用其父类的+load方法。
  • 在所有类的+load 方法调用完以后再调用分类的+load方法,分类的+load方法调用顺序完全按照 Compile Sources 排列顺序。
  • +load 方法还有一个非常重要的特性,那就是子类、父类和分类中的+load方法的实现是被区别对待的。 Runtime 自动调用+load 方法时,分类中的+load 方法并不会对主类中的+load 方法造成覆盖。

你可能感兴趣的:(Objective-C中+load方法的调用历程)