iOS中的+load 与 +initialize

作者: 显生宙 | 来源:发表于2017-08-22 14:30 被阅读227次

    在iOS中,所有的类都继承自NSObject,在这个类中,有两个类方法,分别是 +load 与 +initialize,他们承担着不同的任务,今天详细的解读一下它们各自的意义以及调用原理。

    +load

    load方法会先调用所有父类+load,然后调用子类+load,最后调用分类 +load,分类的调用顺序由 Compile Source决定

    从 runtime的源码来解开+load方法调用的原理。

    首先**void prepare_load_methods(header_info *hi) **函数:

    void prepare_load_methods(header_info *hi)
    {
        size_t count, i;
    
        rwlock_assert_writing(&runtimeLock);
    
        classref_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];
            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);
        }
    }
    

    函数主要作用是准备好被调用的类和分类,具体实现方法在 schedule_class_load(remapClass(classlist[i]));函数中。

    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);中可以看出,每次都传入父类作为参数递归的调用,决定了方法的调用顺序,父类有限,待方法调用完成,会在全局变量 loadable_classesloadable_categories 中。

    当类准备好,接下来调用+load方法,void call_load_methods(void) 函数

    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;
    
        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_class_loads();call_category_loads();中。

    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 = nil;
        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;
            load_method_t load_method = (load_method_t)classes[i].method;
            if (!cls) continue;
    
            if (PrintLoading) {
                _objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
            }
            (*load_method)(cls, SEL_load);
        }
    
        // Destroy the detached list.
        if (classes) _free_internal(classes);
    }
    

    在这个方法中可以看到,使用方法首地址进行直接调用,并非使用objc_msgSend,通过这个方式,我们可以在分类中做一些事情,例如 Method Swizzling

    +initialize

    +initialize 方法会在类收到第一个消息时调用,是一个懒加载的方式,如果一直没收到消息,则永不调用。这种设计节省了资源。

    objc-runtime-new.mm中我们可以看到。

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
                           bool initialize, bool cache, bool resolver)
    {
        ...
            rwlock_unlock_write(&runtimeLock);
        }
    
        if (initialize  &&  !cls->isInitialized()) {
            _class_initialize (_class_getNonMetaClass(cls, inst));
            // If sel == initialize, _class_initialize will send +initialize and 
            // then the messenger will send +initialize again after this 
            // procedure finishes. Of course, if this is not being called 
            // from the messenger then it won't happen. 2778172
        }
    
        // The lock is held to make method-lookup + cache-fill atomic 
        // with respect to method addition. Otherwise, a category could 
        ...
    }
    
    

    当向类发送消息时,如果类没有初始化,则会调用初始化方法 void _class_initialize(Class cls)

    void _class_initialize(Class cls)
    {
        ...
        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_enter(&classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            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]",
                             cls->nameForLogging());
            }
    
            ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: finished +[%s initialize]",
        ...
    }
    

    这里我们可以看出,其实+initialize本质为objc_msgSend,走的是方法调用的流程,如子类没有实现+initialize,则会调用父类+initialize方法,如分类实现了+initialize方法,则会覆盖主类方法(这里的覆盖不是真正的覆盖,主类的方法还存在方法列表中,只是runtime命中对应方法后不会继续向下搜索,则直接调用该方法)。

    我们可以避免父类+initialize方法多次执行

    + (void)initialize {
      if (self == [ClassName self]) {
        // ... do the initialization ...
      }
    }
    

    总结:

    +load +initialize
    调用时机 添加到runtime时 第一次发送消息
    调用顺序 父类->子类->分类 父类->子类(可被分类覆盖)
    调用次数 1 N
    是否需要显式调用父类实现 NO NO
    是否沿用父类的实现 NO YES
    分类中的实现 都执行 如覆盖,不执行主类

    参考链接:
    http://blog.leichunfeng.com/blog/2015/05/02/objective-c-plus-load-vs-plus-initialize/
    https://www.mikeash.com/pyblog/friday-qa-2009-05-22-objective-c-class-loading-and-initialization.html
    http://blog.iderzheng.com/objective-c-load-vs-initialize/
    http://nshipster.com/method-swizzling/

    相关文章

      网友评论

        本文标题:iOS中的+load 与 +initialize

        本文链接:https://www.haomeiwen.com/subject/jszkdxtx.html