×

Objective-C 深入理解 +load 和 +initialize

96
hi_xgb
2016.04.08 21:32* 字数 1136

在 Objective-C 中,NSObject 是绝大多数类的基类。而在 NSObject 中有两个类方法 loadinitialize,那这两个方法是在什么时机被调用呢?父类、Category 的调用顺序又是怎样的呢?下面我们深入 runtime 源码 来一起学习记录下。要是觉得中间部分繁琐,可以直接跳到文末查看结论。

+load

我们先来看 load 是什么时候被调用的,在 load 方法里打断点,看到如下的执行过程

打开 runtime 的源码,我们看到 load_images 的具体实现如下,

/***********************************************************************
* load_images
* Process +load in the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock and loadMethodLock
**********************************************************************/
__private_extern__ const char *
load_images(enum dyld_image_states state, uint32_t infoCount,
            const struct dyld_image_info infoList[])
{
    BOOL found;

    recursive_mutex_lock(&loadMethodLock);

    // Discover load methods
    rwlock_write(&runtimeLock);
    found = load_images_nolock(state, infoCount, infoList);
    rwlock_unlock_write(&runtimeLock);

    // Call +load methods (without runtimeLock - re-entrant)
    if (found) {
        call_load_methods();
    }

    recursive_mutex_unlock(&loadMethodLock);

    return NULL;
}

这里我们发现两个重要的方法 load_images_nolockcall_load_methods,接下来我们再一起查看这两个方法的具体实现。

load_images_nolock
/***********************************************************************
* load_images_nolock
* Prepares +load in the given images which are being mapped in by dyld.
* Returns YES if there are now +load methods to be called by call_load_methods.
*
* Locking: loadMethodLock(both) and runtimeLock(new) acquired by load_images
**********************************************************************/
__private_extern__ BOOL 
load_images_nolock(enum dyld_image_states state,uint32_t infoCount,
                   const struct dyld_image_info infoList[])
{
    BOOL found = NO;
    uint32_t i;

    i = infoCount;
    while (i--) {
        header_info *hi;
        for (hi = FirstHeader; hi != NULL; hi = hi->next) {
            const headerType *mhdr = (headerType*)infoList[i].imageLoadAddress;
            if (hi->mhdr == mhdr) {
                prepare_load_methods(hi);
                found = YES;
            }
        }
    }

    return found;
}
prepare_load_methods
__private_extern__ void prepare_load_methods(header_info *hi)
{
    size_t count, i;

    rwlock_assert_writing(&runtimeLock);

    class_t **classlist = 
        _getObjc2NonlazyClassList(hi, &count);
    for (i = 0; i < count; i++) {
        class_t *cls = remapClass(classlist[i]);
        schedule_class_load(cls);
    }

    category_t **categorylist = _getObjc2NonlazyCategoryList(hi, &count);
    for (i = 0; i < count; i++) {
        category_t *cat = categorylist[i];
        // Do NOT use cat->cls! It may have been remapped.
        class_t *cls = remapClass(cat->cls);
        realizeClass(cls);
        assert(isRealized(cls->isa));
        add_category_to_loadable_list((Category)cat);
    }
}

这里我们发现,class 和 category 被分开处理,先通过 schedule_class_load 将需要执行 load 的 class 添加到一个全局列表里,之后再通过 add_category_to_loadable_list 将需要执行 load 的 category 添加到另一个全局列表里。这两个列表的定义如下,

// List of classes that need +load called (pending superclass +load)
// This list always has superclasses first because of the way it is constructed
static struct loadable_class *loadable_classes NOBSS = NULL;

// List of categories that need +load called (pending parent class +load)
static struct loadable_category *loadable_categories NOBSS = NULL;
schedule_class_load
/***********************************************************************
* prepare_load_methods
* Schedule +load for classes in this image, any un-+load-ed 
* superclasses in other images, and any categories in this image.
**********************************************************************/
// Recursively schedule +load for cls and any un-+load-ed superclasses.
// cls must already be connected.
static void schedule_class_load(class_t *cls)
{
    assert(isRealized(cls));  // _read_images should realize

    if (cls->data->flags & RW_LOADED) return;

    class_t *supercls = getSuperclass(cls);
    if (supercls) schedule_class_load(supercls);

    add_class_to_loadable_list((Class)cls);
    changeInfo(cls, RW_LOADED, 0); 
}

这里我们可以看出,class 的处理是递归处理父类,确保父类先被添加到 loadable_classes 中。至此,两个列表里已经存好了需要执行 load 方法的类和 category。下面再回到 call_load_methods

call_load_methods
__private_extern__ void call_load_methods(void)
{
    static BOOL loading = NO;
    BOOL more_categories;

    recursive_mutex_assert_locked(&loadMethodLock);

    // Re-entrant calls do nothing; the outermost call will finish the job.
    if (loading) return;
    loading = YES;

    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);

    loading = NO;
}

首先从 loadable_classes 中遍历取出类执行 call_class_loads 方法,该方法的具体实现如下

/***********************************************************************
* call_class_loads
* Call all pending class +load methods.
* If new classes become loadable, +load is NOT called for them.
*
* Called only by call_load_methods().
**********************************************************************/
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 = NULL;
    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;
        IMP load_method = classes[i].method;
        if (!cls) continue; 

        if (PrintLoading) {
            _objc_inform("LOAD: +[%s load]\n", _class_getName(cls));
        }
        (*load_method) ((id) cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) _free_internal(classes);
}

这里我们发现了 load 方法的本质,是直接执行函数指针,因此 load 方法不会执行 objc_msgSend 的那一整套流程,objc_msgSend 的完整流程可以看我写的《深入理解 Objective-C 的方法调用流程》

call_category_loads 的最终实现也和 call_class_loads 一样都是直接获取函数指针来执行,这里就不贴源码了。

load 方法调用总结

通过上述源码的分析,我们知道了 load 是在被添加到 runtime 时开始执行,父类最先执行,然后是子类,最后是 Category。又因为是直接获取函数指针来执行,不会像 objc_msgSend 一样会有方法查找的过程。

+initialize

了解了 load 方法的本质,initialize 是不是也是通过直接获取函数指针来执行呢?接下来我们再结合 runtime 源码一起验证下。

initialize 执行的关键代码是 _class_initialize,具体实现如下

/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
*
* Called only from _class_lookupMethodAndLoadCache (or itself).
**********************************************************************/
__private_extern__ void _class_initialize(Class cls)
{
    Class supercls;
    BOOL reallyInitialize = NO;

    // Get the real class from the metaclass. The superclass chain 
    // hangs off the real class only.
    cls = _class_getNonMetaClass(cls);

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = _class_getSuperclass(cls);
    if (supercls  &&  !_class_isInitialized(supercls)) {
        _class_initialize(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    monitor_enter(&classInitLock);
    if (!_class_isInitialized(cls) && !_class_isInitializing(cls)) {
        _class_setInitializing(cls);
        reallyInitialize = YES;
    }
    monitor_exit(&classInitLock);
    
    if (reallyInitialize) {
        // We successfully set the CLS_INITIALIZING bit. Initialize the class.
        
        // Record that we're initializing this class so we can message it.
        _setThisThreadIsInitializingClass(cls);
        
        // Send the +initialize message.
        // Note that +initialize is sent to the superclass (again) if 
        // this class doesn't implement +initialize. 2157218
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: calling +[%s initialize]",
                         _class_getName(cls));
        }

        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        if (PrintInitializing) {
            _objc_inform("INITIALIZE: finished +[%s initialize]",
                         _class_getName(cls));
        }        
        
        // Done initializing. 
        ......
}

通过上述代码,我们发现 initialize 也是取出父类递归执行,确保父类的方法先被执行到。

最关键的是

((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

这里我们发现 initialize 最终是通过 objc_msgSend 来执行的,即 initialize 是会经过一系列方法查找来执行的。

initialize 方法调用总结

initialize 最终是通过 objc_msgSend 来执行的,objc_msgSend 会执行一系列方法查找,并且 Category 的方法会覆盖类中的方法。objc_msgSend 的方法查找流程可以看我写的《深入理解 Objective-C 的方法调用流程》

总结[1]

通过阅读 runtime 的源码,我们知道了 +load+initialize 方法实现的细节,明白了它们的调用机制和各自的特点。下面我们绘制一张表格,以更加直观的方式来巩固我们对它们的理解:

| +load | +initialize |
------ |------ | --- |
调用时机 | 被添加到 runtime 时 |收到第一条消息前,可能永远不调用
调用顺序| 父类->子类->分类 |父类->子类
调用次数| 1次 |多次
是否需要显式调用父类实现| 否| 否
是否沿用父类的实现| 否 |是
分类中的实现 |类和分类都执行 |覆盖类中的方法,只执行分类的实现

这是我写的 runtime 系列文章中的一篇,还有以下几篇从其他方面对 runtime 进行了介绍

  1. iOS runtime之消息转发
  2. iOS runtime 之 Class 和 MetaClass
  3. iOS runtime 之 Category
  4. 深入理解 Objective-C 的方法调用流程

参考资料:

http://blog.leichunfeng.com/blog/2015/05/02/objective-c-plus-load-vs-plus-initialize/

如果您觉得本文对您有所帮助,请点击「喜欢」来支持我。

转载请注明出处,有任何疑问都可联系我,欢迎探讨。


欢迎大家关注我们团队的公众号,不定期分享各类技术干货


image
日记本
Web note ad 1