也谈load与initialize

2017-02-10 14:41
235

一. +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方法,第三步算是处女座的处理,处理异常。

小结

总得来说:

  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;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }

    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }

    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]",
                         cls->nameForLogging());
        }

        // Exceptions: A +initialize call that throws an exception 
        // is deemed to be a complete and successful +initialize.
        @try {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: finished +[%s initialize]",
                             cls->nameForLogging());
            }
        }
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: +[%s initialize] threw an exception",
                             cls->nameForLogging());
            }
            @throw;
        }
        @finally {
            // Done initializing. 
            // If the superclass is also done initializing, then update 
            //   the info bits and notify waiting threads.
            // If not, update them later. (This can happen if this +initialize 
            //   was itself triggered from inside a superclass +initialize.)
            monitor_locker_t lock(classInitLock);
            if (!supercls  ||  supercls->isInitialized()) {
                _finishInitializing(cls, supercls);
            } else {
                _finishInitializingAfter(cls, supercls);
            }
        }
        return;
    }

    else if (cls->isInitializing()) {
        // We couldn't set INITIALIZING because INITIALIZING was already set.
        // If this thread set it earlier, continue normally.
        // If some other thread set it, block until initialize is done.
        // It's ok if INITIALIZING changes to INITIALIZED while we're here, 
        //   because we safely check for INITIALIZED inside the lock 
        //   before blocking.
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else {
            waitForInitializeToComplete(cls);
            return;
        }
    }

    else if (cls->isInitialized()) {
        // Set CLS_INITIALIZING failed because someone else already 
        //   initialized the class. Continue normally.
        // NOTE this check must come AFTER the ISINITIALIZING case.
        // Otherwise: Another thread is initializing this class. ISINITIALIZED 
        //   is false. Skip this clause. Then the other thread finishes 
        //   initialization and sets INITIALIZING=no and INITIALIZED=yes. 
        //   Skip the ISINITIALIZING clause. Die horribly.
        return;
    }

    else {
        // We shouldn't be here. 
        _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方法.
    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也证实了这一想法.

小结

总得来说:

  1. +initialize方法是在main函数之后调用的;
  2. 子类中不需要调用super方法,会自动调用父类的方法实现;
  3. 该方法遵从懒加载方式,只有当当前类第一次接受消息时会触发,只执行一次.
  4. init可多次调用.

三. + load与+ initialize的异同

同:

  1. 都只调用一次
  2. 都不需要调用super方法

异:

  1. +load在main函数之前调用,+initalize在main函数之后调用
  2. 只要一个类中存在+load,无论该类是否接收消息,+load方法都会调用;而initialize只有在该类第一次接收消息时调用.
  3. +load不会主动调用父类中的方法,只是当父类存在+load方法时会首先调用父类,而+ initialize会继承父类方法并主动调用.
帖子评论

关注 / 粉丝

我无话可说