美文网首页
底层原理:Category中的load和initialize方法

底层原理:Category中的load和initialize方法

作者: 飘摇的水草 | 来源:发表于2022-03-09 15:17 被阅读0次
    1. load方法
    1.1 load基本用法

    我们来看一下load基本使用示例

    #import <Foundation/Foundation.h>
    
    @interface People : NSObject
    
    @end
    
    #import "People.h"
    
    @implementation People
    
    + (void)load {
        NSLog(@"%s",__func__);
    }
    
    @end
    
    #import "People.h"
    
    @interface People (Speak)
    
    @end
    
    #import "People+Speak.h"
    
    @implementation People (Speak)
    
    + (void)load {
        NSLog(@"%s",__func__);
    }
    
    @end
    
    #import "People.h"
    
    @interface People (Eat)
    
    @end
    
    #import "People+Eat.h"
    
    @implementation People (Eat)
    
    + (void)load {
        NSLog(@"%s",__func__);
    }
    @end
    
    #import "People.h"
    
    @interface Student : People
    
    @end
    
    #import "Student.h"
    
    @implementation Student
    
    + (void)load
    {
        NSLog(@"%s",__func__);
    }
    @end
    
    #import <Foundation/Foundation.h>
    //#import "People.h"
    //#import "People+Speak.h"
    //#import "People+Eat.h"
    extern void _objc_autoreleasePoolPrint(void);
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
    //        People *people = [[People alloc] init];
        }
        return 0;
    }
    
    

    我们可以发现,仅仅实现了load方法,即使没有使用到类,只要编译完运行,就会调用load方法。为什么呢,接下来我们看一下

    1.2 load调用原理

    +load方法会在runtime加载类、分类时调用
    每个类、分类的+load,在程序运行过程中只调用一次

    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
    
        // fixme defer initialization until an objc-using image is found?
        environ_init();
        tls_init();
        static_init();
        lock_init();
        exception_init();
    
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    }
    //跟进load_images
    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();
    }
    //跟进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;
    }
    

    因为在程序运行时就调用,所以我们也是从runtime初始化方法开始,load调用顺序如下

    1.3 调用顺序
    1. 先调用类的+load

      • 按照编译先后顺序调用(先编译,先调用)
      • 调用子类的+load之前会先调用父类的+load
    2. 再调用分类的+load

      • 按照编译先后顺序调用(先编译,先调用)
    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) { //先调用类的+load
                call_class_loads();
            }
    
            // 2\. Call category +loads ONCE
            more_categories = call_category_loads(); //再调用分类的+load
    
            // 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 all pending class +load methods.
    * If new classes become loadable, +load is NOT called for them.
    *
    * Called only by call_load_methods().
    **********************************************************************/
    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;  //取出classes的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);
    }
    

    Category 和 Class 的 + load 方法的调用顺序规则总结如下:

    1. 先调用主类,按照编译顺序,顺序地根据继承关系由父类向子类调用;
    2. 调用完主类,再调用分类,按照编译顺序,依次调用;
    3. load 方法除非主动调用,否则只会调用一次。

    通过这样的调用规则,我们可以知道:主类的 + load 方法调用一定在分类 + load 方法调用之前。但是分类 + load 方法调用顺序并不是按照继承关系调用的,而是依照编译顺序确定的,这也导致了 + load 方法的调用顺序并不一定确定。一个顺序可能是:

    父类 -> 子类 -> 父类分类 -> 子类分类
    

    也可能是

    父类 -> 子类 -> 子类分类 -> 父类分类
    

    所以student比DGPerson+test先打印,但是不管怎样最先打印的都是person,并且 Load
    方法的调用是早于 main 函数的,如下所示:

    +load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用的,下面我们再看一下initialize方法,如果通过这种方式 [people load] 手动调用load,那就会走消息发送机制。

    +initialize方法
    • +initialize 方法会在类第一次接收到消息时调用,即通过objc_msgSend进行调用,当一个类在查找方法的时候,会先判断当前类是否初始化,如果没有初始化就会去调用initialize方法。
    • 调用顺序
      • 先调用父类的 +initialize 方法,再调用子类的 +initialize 方法
    • +initilize 和 +load 的最大区别是 +initilize 是通过 objc_msgSend 进行调用的,所以有以下特点:
      • 如果子类没有实现 +initilize 方法,会调用父类的 +initilize 方法(所以父类的 +initilize 可能会被调用多次)
      • 如果分类实现了 +initilize 方法,就覆盖类本身的 +initilize 调用。
    • 作用:初始化操作

    可以自己写代码举例证明,这里只是通过runtime源码,去了解一下。
    首先,在runtime源码中,我们创建了一个Person类,在Person类的initalize方法中进行打印,并且加上断点:

    这里直接运行是没有任何输出的,因为上面有提到initialize的调用时机,initialize在类第一次接收到消息时调用,需要在main.m 文件中,调用Person 的class方法:

    此时,我们会看到走到了断点处:

    从上图中我们可以看到,main函数执行之后的流程是:

    • _objc_msgSend_uncached
    • _class_lookupMethodAndLoadCache3
    • lookUpImpOrForward
    • _class_initialize
    • callInitialize
    • [Person initialize]

    我们直接从第3步看就行了,下面是lookUpImpOrForward的方法代码:

    IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                           bool initialize, bool cache, bool resolver)
    {
        ... // 判断这个类是否初始化
        if (initialize  &&  !cls->isInitialized()) {
            runtimeLock.unlock();
            _class_initialize (_class_getNonMetaClass(cls, inst));
            runtimeLock.lock();
            // 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
        }
    ...
    }
    

    cls->isInitialized() 其实就是一个标记这个类是否初始化的标志:

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

    此时我们继续看 _class_initialize 这个方法:

    void _class_initialize(Class cls)
    {
        assert(!cls->isMetaClass());
    
        Class supercls;
        bool reallyInitialize = NO;
    
        // 先调用父类的,再调用子类的
        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;
            }
            
    
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: thread %p: calling +[%s initialize]",
                             pthread_self(), cls->nameForLogging());
            }
    #if __OBJC2__
            @try
    #endif
            {
    // 调用 cls 的initalize 方法
                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()) {
            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()) {
            return;
        }
        
        else {
            _objc_fatal("thread-safe class init in objc runtime is buggy!");
        }
    }
    

    继续看callInitialize 方法:

    void callInitialize(Class cls)
    {
        // 消息查找流程
        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
        asm("");
    }
    

    所以到此非常明了,从源码中我们可以看到

    • 先调用父类的initialize方法,再调用子类的initalize方法,由于initalize方法会走消息查找流程,所以当分类中也实现了initialize方法之后,只会执行分类的initalize方法。
    • 如果本类没有实现initialize方法,父类实现了initialize方法,则多个子类初始化时会多次调用父类的initialize方法,但是本质上只有第一次initialize方法是初始化父类,后面几个initialize都是方法的调用,即子类没有实现,通过superclass到父类里查找。

    完整的+initialize调用流程可参考下图:

    +load和+initialize例子:demo下载地址

    相关文章

      网友评论

          本文标题:底层原理:Category中的load和initialize方法

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