iOS中 性能优化之浅谈load与initialize 韩俊强的博客

一. +load

源码分析

extern bool hasLoadMethods(const headerType *mhdr);
extern void prepare_load_methods(const headerType *mhdr);

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    // Return without taking locks if there are no +load methods here.
    if (!hasLoadMethods((const headerType *)mh)) return;

    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover load methods
    {
        rwlock_writer_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

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

在runtime源码中,我们可以看到,+load方法是在load_images中通过call_load_methods调用的。
更具体的来说是在运行时加载镜像时,通过prepare_load_methods方法将+load方法准备就绪,而后执行call_load_methods,调用+load方法。

1. prepare_load_methods

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;
    runtimeLock.assertWriting();
    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);
    }
}

prepare_load_methods方法中,分为两个步骤:
一是,获取了所有类后,遍历列表,将其中有+load方法的类加入loadable_class
二是,获取所有的类别,遍历列表,将其中有+load方法的类加入loadable_categories.

另外值得注意的一点是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;

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

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}

在该方法中会首先通过schedule_class_load(cls->superclass)确保父类中的 +load方法被加入loadable_class(如果父类有+load方法的话),从而保证父类的+load方法总是在子类之前调用。
也因此,在覆写+load方法时,不需要调用super方法。

2. call_load_methods

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_load_methods方法注释写得非常明了,首先调用类的load方法,在call_class_loads方法中通过在第一步读取prepare_load_methods步骤里的loadable_classes,遍历列表并调用+load方法,然后类似的调用类别的+load方法,第三步算是处女座的处理,处理异常。

3. call_class_loads

static void call_class_loads(void)
{
    int i;

    //1.获取列表
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;

    //2.循环调用load方法
    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; 

        (*load_method)(cls, SEL_load);
    }

    // 3. 释放列表
    if (classes) free(classes);
}

这里我们需要特别注意的是第二部分中:

  (*load_method)(cls, SEL_load);

这段代码也就是说+load方法的调用是通过直接使用函数内存地址的方式实现的,而不是更常见的objc_msgSend来发送消息.

也正是这句代码造就了+load方法的最大特点:类,父类与分类之间+load方法的调用是互不影响的.也就是,子类不会主动调用父类的+load方法,如果类与分类都实现了+load',那么两个+load`方法都会被调用.

小结

iOS中 性能优化之浅谈load与initialize 韩俊强的博客
+load方法的调用顺序图

总得来说:

  1. +load方法是在main函数之前调用的;
  2. 遵从先父类后子类,先本类后列类别的顺序调用;
  3. 类,父类与分类之间的调用是互不影响的.子类中不需要调用super方法,也不会调用父类的+load方法实现;
  4. 无论该类是否接收消息,都会调用+load方法;

二. initialize

源码分析

在NSObject文件中,initialize的实现如下:

+ (void)initialize {
}

然后找到了class_initialize方法,注释表明,当调用class_initialize方法时,就会给当前未初始化的类发送一条 +initialize消息。就是它了。
通过查看caller,我们会看到熟悉的lookUpImpOrForward,也就是消息转发

/***********************************************************************
* class_initialize.  Send the '+initialize' message on demand to any
* uninitialized class. Force initialization of superclasses first.
**********************************************************************/
void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }

    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }

    if (reallyInitialize) {        
        _setThisThreadIsInitializingClass(cls);

        @try {
            callInitialize(cls);
        }
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: +[%s initialize] threw an exception",
                             cls->nameForLogging());
            }
            @throw;
        }
        @finally {
            if (!supercls  ||  supercls->isInitialized()) {
                _finishInitializing(cls, supercls);
            } else {
                _finishInitializingAfter(cls, supercls);
            }
        }
        return;
    }
    else if (cls->isInitializing()) {
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else {
            waitForInitializeToComplete(cls);
            return;
        }
    }
    else if (cls->isInitialized()) {

        return;
    }
    else {
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

_class_initialize方法实现看起来比较长,但其实关键步骤也就只有两步:

  • 确保当前类的父类supercls已经初始化完成 -- 如果没有则通过_class_initialize(supercls)重新进入_class_initialize方法,初始化父类。

  • 处理当前类的初始化状态。

    第一步比较简单,不再赘述,只针对第二个步骤作分析.

    状态处理

当进入第二步,会首先根据当前类的初始化状态决定是否要发送初始化消息.

  • 未初始化
    1) 如果当前类未初始化,则会向它发送一个setInitializing消息,将该类的元类的信息更改为CLS_INITIALIZING,并通过reallyInitialize标识来与Initializing区分.
    2) 成功设置CLS_INITIALIZING后,_setThisThreadIsInitializingClass记录当前线程正在初始化当前类,当前线程可以向该类发送消息,而其他线程则需要等待.
    3) 通过callInitialize调用initialize方法:

  • void callInitialize(Class cls)
    {
     ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
     asm("");
    }
  • 也就是说与+load不同该方法是通过objc_msgSend发送消息实现的,因此也拥有objc_msgSend带来的特性,也就是说子类会继承父类的方法实现,而分类的实现也会覆盖元类.

  • 4) 完成initialize方法后,更新当前类的状态.如果父类已经完成初始化,则_finishInitializing立马更新,否则通过_finishInitializingAfter等父类完成后再更新.

首先来看父类没有完成初始化时的处理 - _finishInitializingAfter :

static void _finishInitializingAfter(Class cls, Class supercls)
{
    PendingInitialize *pending;

    classInitLock.assertLocked();

    if (PrintInitializing) {
        _objc_inform("INITIALIZE: %s waiting for superclass +[%s initialize]",
                     cls->nameForLogging(), supercls->nameForLogging());
    }

    if (!pendingInitializeMap) {
        pendingInitializeMap = 
            NXCreateMapTable(NXPtrValueMapPrototype, 10);
        // fixme pre-size this table for CF/NSObject +initialize
    }

    pending = (PendingInitialize *)malloc(sizeof(*pending));
    pending->subclass = cls;
    pending->next = (PendingInitialize *)
        NXMapGet(pendingInitializeMap, supercls);
    NXMapInsert(pendingInitializeMap, supercls, pending);
}

在该方法中,通过声明一个PendingInitialize类型的结构体pending来存储当前类与父类信息,并以父类supercls为key值,以pending为value存储在pendingInitializeMap链表中.

而如果父类完成了初始化则进入_finishInitializing处理:

static void _finishInitializing(Class cls, Class supercls)
{
    PendingInitialize *pending;
    classInitLock.assertLocked();
    assert(!supercls  ||  supercls->isInitialized());

    if (PrintInitializing) {
        _objc_inform("INITIALIZE: %s is fully +initialized",
                     cls->nameForLogging());
    }

    // mark this class as fully +initialized
    cls->setInitialized();
    classInitLock.notifyAll();
    _setThisThreadIsNotInitializingClass(cls);

    // mark any subclasses that were merely waiting for this class
    if (!pendingInitializeMap) return;
    pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls);
    if (!pending) return;

    NXMapRemove(pendingInitializeMap, cls);

    // Destroy the pending table if it's now empty, to save memory.
    if (NXCountMapTable(pendingInitializeMap) == 0) {
        NXFreeMapTable(pendingInitializeMap);
        pendingInitializeMap = nil;
    }

    while (pending) {
        PendingInitialize *next = pending->next;
        if (pending->subclass) _finishInitializing(pending->subclass, cls);
        free(pending);
        pending = next;
    }
}

在该方法中会首先将当前类标记为已完成初始化状态Initialized,然后去读取pendingInitializeMap,如果查找到该类对应的待处理子类,则将对应的消息移除并通过递归的方法将因当前类而被阻塞的子类标记为已完成初始化.

  • 正在初始化
    如果是当前线程在进行初始化,则不做处理.
    如果是其他线程在进行初始化,则等其他线程完成后再返回,以保证线程安全.

  • 已完成初始化
    如果已经完成初始化,则不做处理.

init

提到初始化方法,不可避免的会想到-init方法.

- (id)init {
    return _objc_rootInit(self);
}

id
_objc_rootInit(id obj)
{
    return obj;
}

由源码可以看到,-init方法并没有次数的限制,这也符合我们之前的认知.

那么+ initialize-init两者的调用顺序又是怎样的呢?
按上文的分析,我推测,由于首先向类发送了alloc消息,此时会触发+ initialize,然后才发送init消息,所以应该是先执行的+ initialize.
通过demo也证实了这一想法.

小结

iOS中 性能优化之浅谈load与initialize 韩俊强的博客
+initialize流程图

总得来说:
1.+initialize方法是在main函数之后调用的;
2.+initialize方法遵从懒加载方式,只有在类或它的子类收到第一条消息之前被调用的;
3.子类中不需要调用super方法,会自动调用父类的方法实现;
4.+initialize只调用一次,init可多次调用.

三. + load与+ initialize的异同

iOS中 性能优化之浅谈load与initialize 韩俊强的博客


iOS中 性能优化之浅谈load与initialize 韩俊强的博客
更多:每周更新关注新浪微博!iOS开发者交流群:446310206

上一篇:[leetcode/lintcode 题解] 阿里算法面试题:猜数字游戏


下一篇:[leetcode/lintcode 题解] 算法面试高频题详解:生命游戏