iOS 底层原理04: +load(), +initialize()

+load()方法

+load() 方法在类/分类第一次加载的时候调用, 并且每个类/分类的+load()方法都会调用

  • 测试1, 父类在前, 子类在后

父类在前, 子类在后

  • 测试2, 父类在后, 子类在前

父类在后, 子类在前

根据前2个测试, 我们可以发现: 不管编译顺序如何, 父类的+load()方法都比子类先调用, 而分类的+load()方法调用顺序与编译顺序有关

源码解析

我们通过源码来研究一下

  1. load_images() 函数

load_images() 函数对于load()方法主要有预加载和调用2个操作,

void load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
        didInitialAttachCategories = true;
        loadAllCategories(); // 加载分类
    }

    // 如果没有 load()方法 直接返回
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        // 预处理load()方法
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    // 调用 load() 方法
    call_load_methods();
}

复制代码
  1. prepare_load_methods() 函数

    • 预加载
    void prepare_load_methods(const headerType *mhdr)
    {
        size_t count, i;
    
        runtimeLock.assertLocked();
    
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(mhdr, &count);
        for (i = 0; i < count; i++) {
            // 加载本类的 load() 方法
            schedule_class_load(remapClass(classlist[i]));
        }
    
        category_t * const *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
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class extensions and categories on Swift "
                            "classes are not allowed to have +load methods");
            }
            realizeClassWithoutSwift(cls, nil);
            ASSERT(cls->ISA()->isRealized());
            
            // 加载分类的load()方法
            add_category_to_loadable_list(cat);
            /**  add_class_to_loadable_list 核心代码
            // 获取 load()方法
            method = _category_getLoadMethod(cat);
    
            // 给list的元素赋值
            loadable_categories[loadable_categories_used].cat = cat;
            loadable_categories[loadable_categories_used].method = method;
    
            // 计数+1
            loadable_categories_used++;
            */
            
        }
    }
    复制代码
    • 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方法加入list , 所以这也就是为什么会先调用父类
        schedule_class_load(cls->getSuperclass());
        
        // 添加到list中
        add_class_to_loadable_list(cls);
        
        /**  add_class_to_loadable_list 核心代码
        // 获取 load()方法
        method = cls->getLoadMethod();
        
        // 给list的元素赋值
        loadable_classes[loadable_classes_used].cls = cls;
        loadable_classes[loadable_classes_used].method = method;
        // 计数+1
        loadable_classes_used++;
        */
        cls->setInfo(RW_LOADED); 
    }
    复制代码

    schedule_class_load() 函数中, 会递归调用到父类, 所以父类的+load()方法会先执行

  2. 调用+load()方法

    • 循环调用+load()方法
    void call_load_methods(void)
    {
        static bool loading = NO;
        bool more_categories;
    
        loadMethodLock.assertLocked();
    
        // Re-entrant calls do nothing; the outermost call will finish the job.
        if (loading) return;
        loading = YES;
    
        void *pool = objc_autoreleasePoolPush();
    
        do {
            // 1. Repeatedly call class +loads until there aren't any more
            while (loadable_classes_used > 0) {
                call_class_loads();
            }
    
            // 2. Call category +loads ONCE
            more_categories = call_category_loads();
    
            // 3. Run more +loads if there are classes OR more untried categories
        } while (loadable_classes_used > 0  ||  more_categories);
    
        objc_autoreleasePoolPop(pool);
    
        loading = NO;
    }
    复制代码
    • call_class_loads()
    static void call_class_loads(void)
    {
        int i;
    
        // Detach current loadable list.
        struct loadable_class *classes = loadable_classes;
        int used = loadable_classes_used;
        loadable_classes = nil;
        loadable_classes_allocated = 0;
        loadable_classes_used = 0;
    
        // Call all +loads for the detached list.
        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; 
    
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
            }
            // 遍历数组, 直接强转后执行load() 函数
            (*load_method)(cls, @selector(load));
        }
    
        // Destroy the detached list.
        if (classes) free(classes);
    }
    复制代码
    • call_category_loads()
    static bool call_category_loads(void)
    {
        int i, shift;
        bool new_categories_added = NO;
    
        // Detach current loadable list.
        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;
    
        // Call all +loads for the detached list.
        for (i = 0; i < used; i++) {
            Category cat = cats[i].cat;
            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));
                }
                // 与call_class_loads相同,  强转后直接调用load()函数
                (*load_method)(cls, @selector(load));
                cats[i].cat = nil;
            }
        }
    
        // ....其他代码
        return new_categories_added;
    }
    复制代码

    从源码我们可以看到, load()是通过C函数直接调用. 所以, 类/分类 的所有load()方法都会执行一次

+initialize()方法

+initialize()方法是在这个类第一次收到消息时调用, 也就是如果不使用到这个类, 就一直不会调用. 我们同样做2个测试

  • 测试1, 父类在前

测试1, 父类在前

  • 测试2, 父类在后

测试2, 父类在后

通过测试我们可以发现, 不管父类在前还是在后, 都会调用到父类分类的+initialize()方法, 但是具体调用哪个分类的+initialize()方法, 却跟编译顺序有关(调用后编译类的方法). 这与上篇文章中, 我们探究分类中方法调用的结果类似. 下边我们就通过源码看下原因

源码解析

  1. lookUpImpOrForward()
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    const IMP forward_imp = (IMP)_objc_msgForward_impcache;
    IMP imp = nil;
    Class curClass;

    runtimeLock.assertUnlocked();
    // 如果初始化过 就进行一次标记
    if (slowpath(!cls->isInitialized())) {
        behavior |= LOOKUP_NOCACHE;
    }

    runtimeLock.lock();
    checkIsKnownClass(cls);
    // 进行cls 的加载
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    // ... 进行其他操作
    return imp;
}
复制代码
  1. realizeAndInitializeIfNeeded_locked()
static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
    runtimeLock.assertLocked();
    if (slowpath(!cls->isRealized())) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
        // runtimeLock may have been dropped but is now locked again
    }

    // 如果cls 还没有初始化过, 进行初始化
    if (slowpath(initialize && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
    }
    return cls;
}
复制代码
  1. initializeAndLeaveLocked()
static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
     // 进行了一次调用
     return initializeAndMaybeRelock(cls, obj, lock, true);
}
复制代码
  1. initializeAndMaybeRelock()
static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
    lock.assertLocked();
    ASSERT(cls->isRealized());

    if (cls->isInitialized()) {
        if (!leaveLocked) lock.unlock();
        return cls;
    }
    Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);

    if (nonmeta->isRealized()) {
        lock.unlock();
    } else {
        nonmeta = realizeClassMaybeSwiftAndUnlock(nonmeta, lock);
        cls = object_getClass(nonmeta);
    }

    ASSERT(nonmeta->isRealized());
    // 通过构造的 nonmetaclass, 进行初始化
    initializeNonMetaClass(nonmeta);

    if (leaveLocked) runtimeLock.lock();
    return cls;
}
复制代码
  1. initializeNonMetaClass() 核心代码
void initializeNonMetaClass(Class cls)
{
    ASSERT(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // 递归调用 父类的初始化方法
    supercls = cls->getSuperclass();
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    
    //  ...其他代码    
    
    if (reallyInitialize) {
        //  ...其他代码
#if __OBJC2__
        @try
#endif
        {   // 初始化class
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                             objc_thread_self(), cls->nameForLogging());
            }
        }
#if __OBJC2__
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                             "threw an exception",
                             objc_thread_self(), cls->nameForLogging());
            }
            @throw;
        }
        @finally
#endif
        {
            // Done initializing.
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }else {
            //  ...其他代码
    }
}
复制代码
  1. callInitialize()
void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}
复制代码
  • 从第5步中我们可以看到, 在进行初始化之前, 会递归进行父类的初始化, 所以 父类的 +initialize() 方法会比子类先调用
  • 从第6步中我们可以看到, 最后进行调用的是 objc_msgSend(), 这就表示最终的流程进入到了 runtime 的消息发送逻辑. 上一篇介绍Category时, 我们已经解释了分类方法会 "覆盖" 本来方法的原因, 这就是调用父类的+initialize() 实际调用的是分类方法的原因

+load(), +initialize()对比

  1. 调用次数
    • 都只会调用一次
  2. 调用时机
    • +load(): 在runtime加载类/分类时调用
    • +initialize(): 在类第一次接收到消息时调用(类初始化时)
  3. 调用原理
    • +load(): 使用C函数直接调用
    • +initialize(): 由runtime 的 objc_msgSend()进行调用
  4. 调用顺序
    • +load(): 所有的类/分类都会调用, 父类比子类先调用, 先编译的分类先调用
    • +initialize(): 父类的方法只会调用一个(父类或父类的某个分类), 父类比子类先调用, 父类的方法会在每个子类初始化时都调用一次(如果子类没有实现)
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享