NSObject中+(void)load运行流程解析

原创文章转载请注明出处,谢谢
两个星期没有更新文章了,实属没有时间的赶脚,确实将自己知道的一些东西整理出来是要比直接讲给别人听要麻烦很多的,好,言归正传。

+(void)load

首先官方的spec里是这么定义的load函数的:

Discussion
The load message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond.

The order of initialization is as follows:

1.All initializers in any framework you link to.

2.All +load methods in your image.(这个image指的就是二进制可执行文件)

3.All C++ static initializers and C/C++ __attribute__(constructor) functions in your image.

4.All initializers in frameworks that link to you.

In addition:

1.A class’s +load method is called after all of its superclasses’ +load methods.

2.A category +load method is called after the class’s own +load method.

In a custom implementation of load you can therefore safely message other unrelated classes from the same image, but any load methods implemented by those classes may not have run yet.

由此我们可以知道load函数是只要你动态加载或者静态引用了这个类,那么load就会被执行,它并不需要你显示的去创建一个类后才会执行,同时只执行一次。

另外就是关于load的执行顺序问题,所有的superclass的load执行完以后才会执行该类的load,以及class中的load方法是先于category中的load执行的。

这里还有关于category多个load方法执行的先后问题,我们到具体讲category的时候再来细说

接下来我们具体看一下+(void)load方法是如何被执行的:

Paste_Image.png

我们可以看到,在执行到+(void)load之前的堆栈信息是这样的,我们可以从堆栈中看出dyld start,然后进入dyld main函数的入口,之后就开始初始化一系列方法,通过ImageLoader将二进制的文件加载进来,最后通知runtime开始执行,以上是我刚开始看到时候的猜想,那么到底是不是这样呢,我们来验证一下。其实dyld也是开源的(dynamic link editor,动态链接器),但是我并没有去看过,因为水平不够嘛,不过未来还是会去看看的。言归正传,这个时候我们看到ImageLoader结束以后开始执行了我们的第一个runtime函数:load_images(其实不是第一个,后面会改正),我们可以来看看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;
}

我们观察一下,这个函数的loadMethodLock都有lock和unlock(很多函数都有这些操作),我猜这就是+(void)load为什么是线程安全的原因,load_images很显然主要是去调用了一个load_images_nolock和call_load_methods的两个函数,如果load_images_nolock返回的是true我们才会去执行call_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;
}

我们通过注释也能明白,它其实就是去查找load这个方法,如果有就通知call_load_methods,那么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++) {
        schedule_class_load(remapClass(classlist[i]));
    }

    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);
        if (!cls) continue;  // category for ignored weak-linked class
        realizeClass(cls);
        assert(isRealized(cls->isa));
        add_category_to_loadable_list((Category)cat);
    }
}

prepare_load_methods其实就是为load方法做准备,我们可以看到class的load是优先于category的load执行的,原因就是class的load会先添加到list里面,之后才回添加category里的load方法,这里我们看看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)
{
    if (!cls) return;
    assert(isRealized(cls));  // _read_images should realize

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

    // Ensure superclass-first ordering
    schedule_class_load(getSuperclass(cls));

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

从这里我们可以看到,子类是需要先将superclass的load方法加载add进列表后才会去添加subclass的load方法,一个递归的过程,所以这就是为什么父类的load会优先于子类执行,同时也说明了子类中为什么不需要显示调用[super load]的原因。然后就是add_class_to_loadable_list和add_category_to_loadable_list两个方法了,但是我暂时不懂多个category中load的执行顺序,因为看上去像是直接按照for循环的顺序来的,等我以后明白了就会补在讲category的文章里。

说到这里,我们又要回到最前面了call_load_methods函数,我们再来看看:

/***********************************************************************
* call_load_methods
* Call all pending class and category +load methods.
* Class +load methods are called superclass-first. 
* Category +load methods are not called until after the parent class's +load.
* 
* This method must be RE-ENTRANT, because a +load could trigger 
* more image mapping. In addition, the superclass-first ordering 
* must be preserved in the face of re-entrant calls. Therefore, 
* only the OUTERMOST call of this function will do anything, and 
* that call will handle all loadable classes, even those generated 
* while it was running.
*
* The sequence below preserves +load ordering in the face of 
* image loading during a +load, and make sure that no 
* +load method is forgotten because it was added during 
* a +load call.
* Sequence:
* 1. Repeatedly call class +loads until there aren't any more
* 2. Call category +loads ONCE.
* 3. Run more +loads if:
*    (a) there are more classes to load, OR
*    (b) there are some potential category +loads that have 
*        still never been attempted.
* Category +loads are only run once to ensure "parent class first" 
* ordering, even if a category +load triggers a new loadable class 
* and a new loadable category attached to that class. 
*
* Locking: loadMethodLock must be held by the caller 
*   All other locks must not be held.
**********************************************************************/
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;
}

这里就是去具体执行load函数了,分为执行class的load函数和category的load函数,两个函数有点复杂具体不细说。

现在到这里和我们的堆栈信息都是一样,不过这里就有个问题了,load_images这个函数到底是怎么开始执行的,换句话说就是是谁开始调用了它呢,所以我们就有必要来看一下了,搜索一下runtime中调用load_images的地方,只有一个叫做_objc_init的地方,我们来看一下函数:

/***********************************************************************
* _objc_init
* Static initializer. Registers our image notifier with dyld.
**********************************************************************/
static __attribute__((constructor))
void _objc_init(void)
{
    // fixme defer initialization until an objc-using image is found?
    environ_init();
    tls_init();
    lock_init();
    exception_init();
        
    // Register for unmap first, in case some +load unmaps something
    _dyld_register_func_for_remove_image(&unmap_image);
    dyld_register_image_state_change_handler(dyld_image_state_bound,
                                             1/*batch*/, &map_images);
    dyld_register_image_state_change_handler(dyld_image_state_dependents_initialized, 0/*not batch*/, &load_images);
}

这个函数一看就是runtime加载的入口函数,通过map_image把image加载到内存里,事实是否如此,我们就来验证一下好了,断点是最能证明的。

Paste_Image.png

果然就像我们说的,最后附上一张所以讲到的函数断点图,同学们可以自己验证一下。

Paste_Image.png

最后就是讲关于+(void)load函数的具体应用了,load函数其实是在main函数之前就回被调用,所以Method Swizzing肯定就是在这个时候调用的,同时比如在运行时动态添加属性,方法甚至类等等,但是千万不要在这里初始化OC的对象,因为load执行的时候你不知道你使用的对象是否已经被加载进来,所以无法预知情况。

最后扯一句废话,写博客其实就是一个自己记录知识的过程,我以前习惯用笔记记,有那么像圣经一样的厚厚的一本,现在回过头来有一半基本都已经忘了,但是自从我写博客记录以来,所有的东西都是记的印象非常深刻,因为这都是自己一句一句码出来的,都是一遍一遍错别字校验来的,所以你自己会有很深的印象,所以如果只是抄别人的博客,那就没什么意思了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容