iOS探尋+(void)load方法的執行時機

load方法中能實現什麼?

咱們常常會有一些需求:讓某方法的執行先於main方法。好比要hook某個系統類的方法(hook UIViewControllerviewWillAppearviewDidDisappear經過AOP的方式實現埋點功能),或者作一些初始化操做。這時候咱們常常首先想到的就是重寫+(void) load {}方法。c++

爲何要放在load方法中?咱們將從objc源碼去探究。sql

程序的裝載

main.m中的main方法被稱做程序的入口。但其實在執行到main以前系統首先要裝載應用程序並完成動態連接,再初始化運行環境,而後準備好調用參數,調用main方法來啓動應用程序並把執行權限交於咱們的代碼。bash

咱們的代碼會引用到UIKitFoundation等框架,還有一些沒有明確import的類庫,好比libSystemlibobjc等。能夠經過otool -L 可執行文件名稱來查看連接的動態庫,他們都是dylib格式。app

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方法。ui

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方法。spa

咱們接着查看call_load_methods方法的實現,在do { } while中,首先調用class_loads,而後再調用category_loadscode

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方法以前,因此可確保時機的要求。sqlite

而且咱們看到,class中的load方法先執行,而後纔會再執行category中重寫的```load``方法

相關文章
相關標籤/搜索