j9九游会登陆--首页直达

Objective-C 深化了解 +load 和 +initialize
  • 智网科技
  • ###
  • 分类: 履历之谈
  • 阅读量: 51

在 Objective-C 中,NSObject 是绝大少数类的基类。而在 NSObject 中有两个类办法 load 和 initialize,那这两个办法是在什么机遇被挪用呢?父类、Category 的挪用次序又是怎样的呢?上面j9九游会深化 runtime 源码 来一同学习记载下。要是以为两头局部繁琐,可以间接跳到文末检察结论。

+load

j9九游会先来看 load 是什么时分被挪用的,在 load 办法里打断点,看到如下的实行历程

1.jpg

翻开 runtime 的源码,j9九游会看到 load_images 的详细完成如下,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
###**********************************************************
* 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;
}

这里j9九游会发明两个紧张的办法 load_images_nolock 和 call_load_methods,接上去j9九游会再一同检察这两个办法的详细完成。

load_images_nolock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
###**********************************************************
* 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__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);
    }
}

这里j9九游会发明,class 和 category 被离开处置,先经过 schedule_class_load 将必要实行 load 的 class 添加到一个全局列内外,之后再经过 add_category_to_loadable_list 将必要实行 load 的 category 添加到另一个全局列内外。这两个列表的界说如下,

1
2
3
4
5
// 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
###**********************************************************
* 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); 
}

这里j9九游会可以看出,class 的处置是递归处置父类,确保父类先被添加到 loadable_classes 中。至此,两个列内外曾经存好了必要实行 load 办法的类和 category。上面再回到 call_load_methods

call_load_methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__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 办法,该办法的详细完成如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
###**********************************************************
* 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);
}

这里j9九游会发明了 load 办法的实质,是间接实行函数指针,因而 load 办法不会实行 objc_msgSend 的那一整套流程,objc_msgSend 的完备流程可以看我写的《深化了解 Objective-C 的办法挪用流程》

call_category_loads 的终极完成也和 call_class_loads 一样都是间接获取函数指针来实行,这里就不贴源码了。

load 办法挪用总结

经过上述源码的剖析,j9九游会晓得了 load 是在被添加到 runtime 时开端实行,父类开始实行,然后是子类,最初是 Category。又由于是间接获取函数指针来实行,不会像 objc_msgSend 一样会无方法查找的历程。

+initialize

理解了 load 办法的实质,initialize 是不是也是经过间接获取函数指针来实行呢?接上去j9九游会再联合 runtime 源码一同验证下。

initialize 实行的要害代码是 _class_initialize,详细完成如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
###**********************************************************
* 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. 
        ......
}

经过上述代码,j9九游会发明 initialize 也是取出父类递归实行,确保父类的办法先被实行到。

最要害的是

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

这里j9九游会发明 initialize 终极是经过 objc_msgSend 来实行的,即 initialize 是会颠末一系列办法查找来实行的。

initialize 办法挪用总结

initialize 终极是经过 objc_msgSend 来实行的,objc_msgSend 会实行一系列办法查找,而且 Category 的办法会掩盖类中的办法。objc_msgSend 的办法查找流程可以看我写的《深化了解 Objective-C 的办法挪用流程》

总结

经过阅读 runtime 的源码,j9九游会晓得了 +load 和 +initialize 办法完成的细节,明确了它们的挪用机制和各自的特点。上面j9九游会绘制一张表格,以愈加直观的方法来牢固j9九游会对它们的了解:

  +load +initialize
挪用机遇 被添加到 runtime 时 收到第一条音讯前,大概永久不挪用
挪用次序 父类->子类->分类 父类->子类
挪用次数 1次 屡次
能否必要显式挪用父类完成
能否相沿父类的完成
分类中的完成 类和分类都实行 掩盖类中的办法,只实行分类的完成

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

参考材料: