美文网首页
详解load 函数

详解load 函数

作者: 杨柳小易 | 来源:发表于2018-01-19 15:28 被阅读55次

详解load 函数

因为下载了一个可以调试的runtime代码,所以重新阅读以下runtime的一些代码,写点栗子跑一跑

众所周知,load 的调用时机是这个class加载的时候就调用的,这篇文章主要弄清楚

  1. load调用的具体时机
  2. 父类子类的load调用 顺序
  3. 分类 和 原始类的 load 调用顺序
  4. 子类分类load 的调用顺序

暂时就这么多。ok,我们随便建立一个工程,

@implementation ViewController

+ (void)load {
    NSLog(@"ViewController");
}

@end


@interface sonViewController : ViewController
@end


@implementation sonViewController

+ (void)load {
    NSLog(@"sonViewController");
}

@end

@implementation ViewController (ll)

+ (void)load {
    NSLog(@"ViewController (ll)");
}

@end

@implementation sonViewController (ll)

+ (void)load {
    NSLog(@"sonViewController (ll)");
}

@end

我们看看打印结果:


ViewController
sonViewController
ViewController (ll)
sonViewController (ll)

我们可以清楚的看到,调用顺序是这样子的。我们可以通过其他手段来看看,为什么是这种调用顺序。从git 上下载一份可以调试的runtime源码。

https://github.com/0xxd0/objc4

运行,没有问题的话我们就可以巴拉巴拉这个代码了。

重点在这个函数

从函数命名可以看出,是调用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) {
            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;
}

从这个函数可以看出, 在一个 do {}while 循环里面,做的事情,
不断的尝试调用 call_class_loads ,全局有一个列表,来标示,稍后分析。

然后才调用分类的load函数。

more_categories = call_category_loads();

调用分类的load 函数。。

此处,我们能得出结论,先调用所有已经加载类的load函数,然后调用分类的load.那么子类和父类的调用关系在哪里看呢?

我们查看调用类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方法
        (*load_method)(cls, SEL_load);
    }
    
    // Destroy the detached list.
    if (classes) free(classes);
}

从这里,我们可以看到,就是全局有一个loadable_classes 数组,然后顺序遍历这个数组,调用对应的load方法。所以,父类子类的关系,肯定是父类子类放入表中的时机不一样。我们重点关注一下,放入先后顺序问题上。

调用顺序如下:

{
    load_images
         prepare_load_methods
            schedule_class_load
            add_category_to_loadable_list
}

通过 prepare_load_methods 函数,可以看出,先把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); 
}

把一个类add_class_to_loadable_list 之前,先尝试 superView schedule_class_load(cls->superclass);

如果superView 已经加载过了就return;

这就是为什么super的load 比子类的load调用时机早

分类的load方法调用顺序,完全取决于 _getObjc2NonlazyCategoryList 获取到的分类顺序啦!

我们来理一理重要函数的代码模块

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);
}

runtime 一开始会执行这个函数,这个函数做了两件事情,初始化工作,和 注册事件,通过 _dyld_objc_notify_register 注册了 load_images 函数,如果有新的镜像被加入,load_images 函数就会执行。那么load_images 函数里干了什么事情?

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    recursive_mutex_locker_t lock(loadMethodLock);

    // Discover +load methods
    prepare_load_methods((const headerType *)mh);

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

Mach-O是OSX和iOS上的可执行二进制文件格式:Mach-Object 这里struct mach_header 是 Mach-Object 的header

prepare_load_methods 字面意思上看,就是发现类有没有实现load函数。
call_load_methods 调用上面发现的load函数。

load 的代替方案,

使用initialize 代替

initialize,可以延迟调用时机,如果app中大量使用了load 有可能导致启动慢

使用

__attribute__ 

<code>attribute</code> 是GNU C特色之一,在iOS用的比较广泛。如果你没有用过,那系统库你总用过,在Foundation.framework中有很多地方用到attribute特性。attribute 可以设置函数属性(Function Attribute )、变量属性(Variable Attribute )和类型属性(Type Attribute)

比如我们下面的代码

@interface TDDObj : NSObject
@end

@implementation TDDObj
+ (void)load {
    NSLog(@"1111");
}
@end

__attribute((constructor)) static void beforeMainCall(void) {
    NSLog(@"11111");
}


int main(int argc, char * argv[]) {
    @autoreleasepool {
    @try
        {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
        @catch(NSException* exception)
        {
            PTVLog(@"exception:%@", exception);
        }
    }
}

我们通过断点或者观察输出log,可以看到,TDDObj 的 load 函数先调用,然后 beforeMainCall 调用,接着,main 函数调用了。
通过这种手段,可以做一些有趣的事情,比如遍历所有类,如果实现了谋改革协议或者方法做些事情,如果所有的类load里面做完事情,要进一步在 main 之前处理,就可以通过这种手段。

相关文章

网友评论

      本文标题:详解load 函数

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