美文网首页
OC底层原理(六):Load和Initialize

OC底层原理(六):Load和Initialize

作者: 跳跳跳跳跳跳跳 | 来源:发表于2021-01-14 22:10 被阅读0次

Load方法什么时候调用?

load方法会在runtime加载类、分类的时候调用,且仅加载一次。
我们创建一个命令行项目,然后创建一个ZJPerson类和它的两个分类,Study、Life

@interface ZJPerson : NSObject

@end

@implementation ZJPerson
+ (void)load {
    NSLog(@"ZJPerson Load");
}
@end
@interface ZJPerson (Study)

@end

@implementation ZJPerson (Study)
+ (void)load {
    NSLog(@"ZJPerson(Study) Load");
}
@end
@interface ZJPerson (Life)

@end

@implementation ZJPerson (Life)
+ (void)load {
    NSLog(@"ZJPerson(Life) Load");
}
@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
    return 0;
}

main函数中什么也不写,然后运行


截屏2021-01-11 21.03.44.png

为什么Load方法没有像分类的实例方法、类方法一样进行覆盖呢?
我们打开runtime源码来看
打开objc-os.mm文件,找到_objc_init方法

void _objc_init(void)
{
    ……
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    ……
}

这次我们点击进入load_images方法

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    ……

    // Discover load methods
    {
        mutex_locker_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)
{
    ……

    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
        //调用分类的load方法
        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_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方法
        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方法
        (*load_method)(cls, @selector(load));
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

从源码中我们可以看到是先调用类的load方法再调用分类的load方法
load方法并不是通过消息发送机制来调用的,而是直接访问地址来调用

Load方法调用顺序

在call_class_loads方法中我们可以看到是按顺序依次从loadable_classes数组中取出类再调用load方法的,那么loadable_classes数组又是在哪里加的呢?
我们需要回到上一层方法

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    ……
    // Discover load methods
    {
        mutex_locker_t lock2(runtimeLock);
        //将类添加到loadable_classes中
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}

点击进入prepare_load_methods方法

void prepare_load_methods(const headerType *mhdr)
{
    ……
    //按照编译顺序取出类的数组列表
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        //依次遍历数组列表,将类添加到loadable_classes中
        schedule_class_load(remapClass(classlist[i]));
    }
    //按照编译顺序取出分类的数组列表
    category_t * const *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
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class extensions and categories on Swift "
                        "classes are not allowed to have +load methods");
        }
        realizeClassWithoutSwift(cls, nil);
        ASSERT(cls->ISA()->isRealized());
        //依次遍历数组列表,将分类添加到loadable_categories中
        add_category_to_loadable_list(cat);
    }
}

我们继续点击进入schedule_class_load方法

static void schedule_class_load(Class cls)
{
    ……
    // Ensure superclass-first ordering
    //递归调用
    schedule_class_load(cls->getSuperclass());
    //添加类进数组
    add_class_to_loadable_list(cls);
    …… 
}
void add_class_to_loadable_list(Class cls)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = cls->getLoadMethod();
    if (!method) return;  // Don't bother if cls has no +load method
    
    if (PrintLoading) {
        _objc_inform("LOAD: class '%s' scheduled for +load", 
                     cls->nameForLogging());
    }
    
    if (loadable_classes_used == loadable_classes_allocated) {
        loadable_classes_allocated = loadable_classes_allocated*2 + 16;
        loadable_classes = (struct loadable_class *)
            realloc(loadable_classes,
                              loadable_classes_allocated *
                              sizeof(struct loadable_class));
    }
    //依次添加类进数组
    loadable_classes[loadable_classes_used].cls = cls;
    loadable_classes[loadable_classes_used].method = method;
    loadable_classes_used++;
}
void add_category_to_loadable_list(Category cat)
{
    IMP method;

    loadMethodLock.assertLocked();

    method = _category_getLoadMethod(cat);

    // Don't bother if cat has no +load method
    if (!method) return;

    if (PrintLoading) {
        _objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
                     _category_getClassName(cat), _category_getName(cat));
    }
    
    if (loadable_categories_used == loadable_categories_allocated) {
        loadable_categories_allocated = loadable_categories_allocated*2 + 16;
        loadable_categories = (struct loadable_category *)
            realloc(loadable_categories,
                              loadable_categories_allocated *
                              sizeof(struct loadable_category));
    }
    //依次添加分类进数组
    loadable_categories[loadable_categories_used].cat = cat;
    loadable_categories[loadable_categories_used].method = method;
    loadable_categories_used++;
}

从上面的源码我们可以看出,当一个类是子类时会优先添加其父类进数组,之后再添加子类进数组,而别的类则是按编译顺序添加进数组

调用顺序总结
  • 优先调用类的load方法
    1. 按照编译顺序调用load,先编译先调用load
    2. 在调用子类的load之前会先调用父类的load
  • 其次调用分类的load方法
    1. 按照编译顺序调用load,先编译先调用load

我们通过代码来验证下,新增两个类ZJCat、ZJDog和ZJPerson的子类ZJStudent

@interface ZJCat : NSObject

@end

@implementation ZJCat
+ (void)load {
    NSLog(@"ZJCat Load");
}
@end
@interface ZJDog : NSObject

@end

@implementation ZJDog
+ (void)load {
    NSLog(@"ZJDog Load");
}
@end
@interface ZJStudent : ZJPerson

@end

@implementation ZJStudent
+ (void)load {
    NSLog(@"ZJStudent Load");
}
@end

我们故意把ZJStudent的编译顺序放在ZJPerson的前面


截屏2021-01-12 21.32.48.png

然后运行项目,注意比较ZJDog、ZJStudent、ZJCat的顺序以及ZJStudent和ZJPerson的顺序,按照上面的顺序逻辑来推导,其打印结果应该是
ZJDog -> ZJPerson -> ZJStudent -> ZJCat -> ZJPerson(Life) -> ZJPerson(Study)


截屏2021-01-12 21.36.21.png
可以看到确实是我们分析的那个顺序
我们再调整顺序重新运行
截屏2021-01-12 21.40.32.png

按照逻辑分析,其打印顺序应该为
ZJPerson -> ZJCat -> ZJDog -> ZJStudent -> ZJPerson(Life) -> ZJPerson(Study)


截屏2021-01-12 21.42.02.png

Initialize什么时候调用?

initialize会在类第一次收到消息的时候,且只会调用一次
更新ZJPerson、Study分类、Life分类、ZJCat、ZJDog、ZJStudent代码至如下

@interface ZJPerson : NSObject

@end

@implementation ZJPerson
+ (void)initialize {
    NSLog(@"ZJPerson initalize");
}
@end
@interface ZJPerson (Study)

@end

@implementation ZJPerson (Study)
+ (void)initialize {
    NSLog(@"ZJPerson (Study) initalize");
}
@end
@interface ZJPerson (Life)

@end

@implementation ZJPerson (Life)
+ (void)initialize {
    NSLog(@"ZJPerson (Life) initalize");
}
@end
@interface ZJCat : NSObject

@end

@implementation ZJCat
+ (void)initialize {
    NSLog(@"ZJCat initalize");
}
@end
@interface ZJDog : NSObject

@end

@implementation ZJDog
+ (void)initialize {
    NSLog(@"ZJDog initalize");
}
@end
@interface ZJStudent : ZJPerson

@end

@implementation ZJStudent
+ (void)initialize {
    NSLog(@"ZJStudent initalize");
}
@end

然后main函数中什么也不写,运行项目

int main(int argc, const char * argv[]) {
    @autoreleasepool {
    }
    return 0;
}
截屏2021-01-14 21.13.59.png

什么也没有打印
我们在main函数中初始化ZJPerson类

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [ZJPerson alloc];
    }
    return 0;
}

在运行项目


截屏2021-01-14 21.20.57.png

调用的确实分类的initialize方法,所以我们可以肯定在类第一次收到消息的时候,会在类查找方法的时候通过objc_msgSend来调用initialize方法
我们打开源码看下,搜索class_getInstanceMethod方法

Method class_getInstanceMethod(Class cls, SEL sel)
{
    ……
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER);
    ……
}

点击进入lookUpImpOrForward方法

IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
{
    ……
    cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
    ……
}

继续点击进入realizeAndInitializeIfNeeded_locked方法

static Class
realizeAndInitializeIfNeeded_locked(id inst, Class cls, bool initialize)
{
    ……

    if (slowpath(initialize && !cls->isInitialized())) {
        //没有初始化
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock);
        ……
    }
    return cls;
}

继续点击进入initializeAndLeaveLocked方法

static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock)
{
    return initializeAndMaybeRelock(cls, obj, lock, true);
}

static Class initializeAndMaybeRelock(Class cls, id inst,
                                      mutex_t& lock, bool leaveLocked)
{
   ……
    initializeNonMetaClass(nonmeta);
    ……
}

继续点击进入initializeNonMetaClass方法

void initializeNonMetaClass(Class cls)
{
    ……
    supercls = cls->getSuperclass();
    //递归调用,优先初始化父类
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    ……
            callInitialize(cls);
    ……
}

点击进入callInitialize方法

void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}

通过源码我们可以看到

  • 调用子类initialize之前会先调用父类的initialize
  • initialize是通过objc_msgSend消息发送机制来调用的
  • 如果父类实现的initialize方法,而子类没有实现,则initialize方法会调用多次
  • 如果分类实现了initialize方法会覆盖原有类的initialize方法

面试题

  1. load方法在什么时候调用?
    load方法在runtime加载类、分类的时候调用

  2. load和initialize的区别?
    调用时机
    load方法在runtime加载类、分类的时候调用
    initialize方法在类第一次收到消息时调用

    调用顺序
    load调用顺序

  • 优先调用类的load方法

    1. 按照编译顺序调用load,先编译先调用load
    2. 在调用子类的load之前会先调用父类的load
  • 其次调用分类的load方法

    1. 按照编译顺序调用load,先编译先调用load

    initialize调用顺序
    在调用子类的initialize之前会先调用父类的initialize

    调用方式
    load方法是runtime加载类、分类时通过load函数地址直接调用
    initialize方法是通过objc_msgSend消息发送机制来调用的

相关文章

网友评论

      本文标题:OC底层原理(六):Load和Initialize

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