iOS探寻+(void)load方法的执行时机

load方法中能实现什么?

我们经常会有一些需求:让某方法的执行先于main方法。比如要hook某个系统类的方法(hook UIViewControllerviewWillAppearviewDidDisappear通过AOP的方式实现埋点功能),或者做一些初始化操作。这时候我们经常首先想到的就是重写+(void) load {}方法。

为什么要放在load方法中?我们将从objc源码去探究。

程序的装载

main.m中的main方法被称作程序的入口。但其实在执行到main之前系统首先要装载应用程序并完成动态链接,再初始化运行环境,然后准备好调用参数,调用main方法来启动应用程序并把执行权限交于我们的代码。

我们的代码会引用到UIKitFoundation等框架,还有一些没有明确import的类库,比如libSystemlibobjc等。可以通过otool -L 可执行文件名称来查看链接的动态库,他们都是dylib格式。

otool -L XXApp.app/XXApp 
复制代码
XXApp.app/XXApp:
    /usr/lib/libbz2.1.0.dylib (compatibility version 1.0.0, current version 1.0.5)
    /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 400.9.4)
    /usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
    /usr/lib/libicucore.A.dylib (compatibility version 1.0.0, current version 62.1.0)
    /usr/lib/libprotobuf.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/libresolv.9.dylib (compatibility version 1.0.0, current version 1.0.0)
    /usr/lib/libsqlite3.dylib (compatibility version 9.0.0, current version 274.22.0)
    /usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.11)
    /System/Library/Frameworks/ARKit.framework/ARKit (compatibility version 1.0.0, current version 1.0.0)
    /System/Library/Frameworks/AVFoundation.framework/AVFoundation (compatibility version 1.0.0, current version 2.0.0)
复制代码
动态链接库dyld

dyld是动态链接器(dynamic link editor),是苹果的动态连接器。系统先读取App的可执行文件(Mach-O),从里面获得dyld的路径,然后加载dyld,然后加载程序相关依赖库,对库进行链接,最后调用每个依赖库的初始化方法。ojbc的初始化就发生在这个时机。

objc setup

objc的初始化主要在_objc_init方法中完成。

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);
}
复制代码

这里我们可以看到,objc向dyld注册了三个callback函数。当我们的可执行文件的image load 后会执行load_images方法。

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
    {
        mutex_locker_t lock2(runtimeLock);
        prepare_load_methods((const headerType *)mh);
    }

    // Call +load methods (without runtimeLock - re-entrant)
    call_load_methods();
}
复制代码

load_images方法中主要做了两件事情prepare_load_methods,然后call_load_methods。其实就是准备好load方法,然后调用(call)load方法。

我们接着查看call_load_methods方法的实现,在do { } while中,首先调用class_loads,然后再调用category_loads

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;
}
复制代码

load_images的执行发生在main方法之前,所以可确保时机的要求。

并且我们看到,class中的load方法先执行,然后才会再执行category中重写的```load``方法

转载于:https://juejin.im/post/5d3ff5be6fb9a06b0517c915

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值