美文网首页
+load方法与+initialize方法的区别

+load方法与+initialize方法的区别

作者: CoderLS | 来源:发表于2018-11-20 13:41 被阅读0次

    两个方法的区别

    1.两个方法的调用方式

    load是拿到函数地址直接进行调用
    initialize是通过objc_msgSend()进行调用的

    2.两个方法的调用时机

    load是runtime加载类,分类的时候调用的(只调用一次)
    initialize是类第一次接收到消息时调用的,而且每个类只能被初始化一次(父类initialize方法可能被调用多次)

    3.两个方法的调用顺序

    • load

      1. 先调用类的load方法
        先编译的类优先调用
        调用子类的load的之前,会先调用父类的load方法
      2. 再调用分类的load方法
        先编译的分类优先先调用(只看编译顺序,不区分是父类的分类还是子类的分类)
    • initialize

      先初始化父类
      再初始化子类(可能最终调用的是父类的initialize方法)

    load源码分析步骤:

    objc4源码解读过程:objc-os.mm
    _objc_init
    load_images
    prepare_load_methods
    schedule_class_load
    add_class_to_loadable_list
    add_category_to_loadable_list
    call_load_methods
    call_class_loads
    call_category_loads
    (*load_method)(cls, SEL_load)
    
    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(classes);
    }
    

    从上面看到+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用,loadable_classes里存放着很多下面这种结构体:
    cls也就是哪个类,method就是load方法
    struct loadable_class {
    Class cls;
    IMP method;
    };

    遍历loadable_classes数组,拿到load方法地址直接调用,那么也就是数组的顺序就是调用load方法的顺序,那就接着看看这个数组是怎么添加的,看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);
        }
    }
    

    看到这个函数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);这行代码会递归将父类填到到这个列表,然后再添加当前类,
    if (cls->data()->flags & RW_LOADED) return;
    这行代码是判断是否添加过,如果添加过直接return,保证每个类只调用一次load方法
    cls->setInfo(RW_LOADED); 这行代码是将类添加到数组里时设置的标识位用来判断是否添加过

    而分类就是正常遍历直接添加,所以就是按照编译顺序调用的,先编译先调用
    综上大家应该了解了+load方法调用的底层结构了,如果手动调用[self load]方法,调用的还是objc_msgSend(),则按照正常方法调用步骤,找isa,superclass,一步步寻找方法进行调用,load方法可能会被分类覆盖,但是一般不需要手动调用,

    +initialize源码分析步骤:

    objc4源码解读过程
    objc-msg-arm64.s
    objc_msgSend
    objc-runtime-new.mm
    class_getInstanceMethod
    lookUpImpOrNil
    lookUpImpOrForward
    _class_initialize
    callInitialize
    objc_msgSend(cls, SEL_initialize)
    
    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        IMP imp = nil;
        bool triedResolver = NO;
        runtimeLock.assertUnlocked();
        // Optimistic cache lookup
        if (cache) {
            imp = cache_getImp(cls, sel);
            if (imp) return imp;
        }
        // runtimeLock is held during isRealized and isInitialized checking
        // to prevent races against concurrent realization.
        // runtimeLock is held during method search to make
        // method-lookup + cache-fill atomic with respect to method addition.
        // Otherwise, a category could be added but ignored indefinitely because
        // the cache was re-filled with the old value after the cache flush on
        // behalf of the category.
        runtimeLock.read();
        if (!cls->isRealized()) {
            // Drop the read-lock and acquire the write-lock.
            // realizeClass() checks isRealized() again to prevent
            // a race while the lock is down.
            runtimeLock.unlockRead();
            runtimeLock.write();
            realizeClass(cls);
            runtimeLock.unlockWrite();
            runtimeLock.read();
        }
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlockRead();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.read();
            // 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
        }
     retry:    
        runtimeLock.assertReading();
        // Try this class's cache.
        imp = cache_getImp(cls, sel);
        if (imp) goto done;
        // Try this class's method lists.
        {
            Method meth = getMethodNoSuper_nolock(cls, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, cls);
                imp = meth->imp;
                goto done;
            }
        }
        // Try superclass caches and method lists.
        {
            unsigned attempts = unreasonableClassCount();
            for (Class curClass = cls->superclass;
                 curClass != nil;
                 curClass = curClass->superclass)
            {
                // Halt if there is a cycle in the superclass chain.
                if (--attempts == 0) {
                    _objc_fatal("Memory corruption in class list.");
                }
                
                // Superclass cache.
                imp = cache_getImp(curClass, sel);
                if (imp) {
                    if (imp != (IMP)_objc_msgForward_impcache) {
                        // Found the method in a superclass. Cache it in this class.
                        log_and_fill_cache(cls, imp, sel, inst, curClass);
                        goto done;
                    }
                    else {
                        // Found a forward:: entry in a superclass.
                        // Stop searching, but don't cache yet; call method 
                        // resolver for this class first.
                        break;
                    }
                }
                
                // Superclass method list.
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                    imp = meth->imp;
                    goto done;
                }
            }
        }
    
        // No implementation found. Try method resolver once.
    
        if (resolver  &&  !triedResolver) {
            runtimeLock.unlockRead();
            _class_resolveMethod(cls, sel, inst);
            runtimeLock.read();
            // Don't cache the result; we don't hold the lock so it may have 
            // changed already. Re-do the search from scratch instead.
            triedResolver = YES;
            goto retry;
        }
    
        // No implementation found, and method resolver didn't help. 
        // Use forwarding.
    
        imp = (IMP)_objc_msgForward_impcache;
        cache_fill(cls, sel, imp, inst);
    
     done:
        runtimeLock.unlockRead();
    
        return imp;
    }
    
    • 看上面的源码可以看到下面片段代码,会先判断当前类是否初始化过,initialize参数系统传的是YES,所以当前类没初始化过就调用_class_initialize()
     if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlockRead();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.read();
            // 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
        }
    

    _class_initialize源码如下

    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);
    
            if (MultithreadedForkChild) {
                // LOL JK we don't really call +initialize methods after fork().
                performForkChildInitialize(cls, supercls);
                return;
            }
            
            // 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: thread %p: calling +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
    
            // Exceptions: A +initialize call that throws an exception 
            // is deemed to be a complete and successful +initialize.
            //
            // Only __OBJC2__ adds these handlers. !__OBJC2__ has a
            // bootstrapping problem of this versus CF's call to
            // objc_exception_set_functions().
    #if __OBJC2__
            @try
    #endif
            {
                callInitialize(cls);
    
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                                 pthread_self(), cls->nameForLogging());
                }
            }
    #if __OBJC2__
            @catch (...) {
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                                 "threw an exception",
                                 pthread_self(), cls->nameForLogging());
                }
                @throw;
            }
            @finally
    #endif
            {
                // Done initializing.
                lockAndFinishInitializing(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 if (!MultithreadedForkChild) {
                waitForInitializeToComplete(cls);
                return;
            } else {
                // We're on the child side of fork(), facing a class that
                // was initializing by some other thread when fork() was called.
                _setThisThreadIsInitializingClass(cls);
                performForkChildInitialize(cls, supercls);
            }
        }
        
        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!");
        }
    }
    
    

    下面代码是一部分可以看到,初始化当前类时会先判断父类是否为真,父类是否初始化,如果未初始化去初始化父类,递归调用。

    supercls = cls->superclass;
        if (supercls  &&  !supercls->isInitialized()) {
            _class_initialize(supercls);
        }
    
    • 父类初始化完成在初始化当前类调用callInitialize,如下面代码所示,初始化完成调用lockAndFinishInitializing

    callInitialize源码

    • 如下,可以看到是掉用的objc_msgSend()
    void callInitialize(Class cls)
    {
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
        asm("");
    }
    
    #if __OBJC2__
            @try
    #endif
            {
                callInitialize(cls);
    
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
                                 pthread_self(), cls->nameForLogging());
                }
            }
    #if __OBJC2__
            @catch (...) {
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: thread %p: +[%s initialize] "
                                 "threw an exception",
                                 pthread_self(), cls->nameForLogging());
                }
                @throw;
            }
            @finally
    #endif
            {
                // Done initializing.
                lockAndFinishInitializing(cls, supercls);
            }
            return;
        }
    

    lockAndFinishInitializing源码,将类标识为已初始化将flags= RW_INITIALIZED,然后判断是否初始化代码:

    bool isInitialized() {
    return getMeta()->data()->flags & RW_INITIALIZED;
    }

    static void lockAndFinishInitializing(Class cls, Class supercls)
    {
        monitor_locker_t lock(classInitLock);
        if (!supercls  ||  supercls->isInitialized()) {
            _finishInitializing(cls, supercls);
        } else {
            _finishInitializingAfter(cls, supercls);
        }
    }
    

    如果你看明白了上面的原理,看看下面这种情况会打印什么,如果你还能猜到打印什么,那证明你真的看懂了原理,

    @interface LSPerson : NSObject
    @end
    @implementation LSPerson
    + (void)initialize{
        NSLog(@"LSPerson +initialize");
    }
    @end
    
    
    
    @interface LSStudent : LSPerson
    @end
    @implementation LSStudent
    + (void)initialize{
        NSLog(@"LSStudent +initialize");
    }
    @end
    
    
    
    @interface LSTeacher : LSPerson
    @end
    @implementation LSTeacher
    + (void)initialize{
        NSLog(@"LSTeacher +initialize");
    }
    @end
    
    • 1.第一种情况 正常运行,结果在最下面
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [LSStudent alloc];
            [LSTeacher alloc];
        }
        return 0;
    }
    
    • 2.第二种情况 将 LSStudent里的load方法注释掉,然后运行
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [LSStudent alloc];
            [LSTeacher alloc];
        }
        return 0;
    }
    
    • 3.第三种情况 将 LSStudent里的load方法注释掉,将 LSTeacher里的load方法也注释掉,然后运行
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [LSStudent alloc];
            [LSTeacher alloc];
        }
        return 0;
    }
    
    • 4.第四种情况 将 LSStudent里的load方法注释掉,将 LSTeacher里的load方法也注释掉,然后运行
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            [LSStudent initialize];
            [LSTeacher initialize];
        }
        return 0;
    }
    
    • 第一种情况打印结果:都不注释

    LSPerson +initialize
    LSStudent +initialize
    LSTeacher +initialize

    • 第二种情况打印结果:注释LSStudent的initialize方法

    LSPerson +initialize
    LSPerson +initialize
    LSTeacher +initialize

    • 第三种情况打印结果:注释LSStudent的initialize方法,注释LSTeacher的initialize方法

    LSPerson +initialize
    LSPerson +initialize
    LSPerson +initialize

    • 第四种情况打印结果:

    LSPerson +initialize
    LSPerson +initialize
    LSPerson +initialize
    LSPerson +initialize
    LSPerson +initialize

    以下是调用
    [LSStudent alloc];
    [LSTeacher alloc];
    的伪代码,如果是objc_msgSend([LSStudent class],@selector(initialize)),而student类没有就会从父类找,所以掉了父类的initialize方法,并不是初始化了多次,teacher也是类似,而第四种情况是,类第一次收到消息触发initialize方法,然后所有逻辑都走完之后再去调用你真正想调用的initialize方法,所以比第三种情况多2打印了遍

            BOOL sutdentInitialized = NO;
            BOOL personInitialized = NO;
            BOOL teacherInitialized = NO;
            
            if (!sutdentInitialized) {
                if (!personInitialized) {
                    objc_msgSend(LSStudent.superclass, @selector(initialize));
                    personInitialized = YES;
                }
                objc_msgSend([LSStudent class], @selector(initialize));
                sutdentInitialized = YES;
            }
            
            
            if (!teacherInitialized) {
                if (!personInitialized) {
                    objc_msgSend(LSTeacher.superclass, @selector(initialize));
                    personInitialized = YES;
                }
    
                objc_msgSend([LSTeacher class], @selector(initialize));
                teacherInitialized = YES;
            }
    

    最后附上两个方法的分析结论和步骤图

    load方法

    屏幕快照 2018-11-21 下午2.19.21.png

    initialize方法 屏幕快照 2018-11-21 下午2.19.27.png

    相关文章

      网友评论

          本文标题:+load方法与+initialize方法的区别

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