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

1. load方法
1.1 load基本用法

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

#import 

@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 
//#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方法)