load與initialize

不會騎名字發表於2018-06-11

我們都知道我們每個app的入口函式是mian函式,那麼在main函式呼叫前都執行了什麼?

dyld

iOS中用到的所有系統 framework 都是動態連結的,在執行的時候我們可以在lldb中使用

image list -o -f
複製程式碼

檢視專案連結的動態庫

load與initialize
大多數的lib都是dylib格式,系統使用動態連結有幾點好處:

  • 程式碼共用:很多程式都動態連結了這些 lib,但它們在記憶體和磁碟中中只有一份
  • 易於維護:由於被依賴的 lib 是程式執行時才 link 的,所以這些 lib 很容易做更新,比如libSystem.dyliblibSystem.B.dylib 的替身,哪天想升級直接換成 libSystem.C.dylib 然後再替換替身就行了
  • 減少可執行檔案體積:相比靜態連結,動態連結在編譯時不需要打進去,所以可執行檔案的體積要小很多

摘錄孫源大神

在進行iOS逆向工程的時候如果想要在非越獄機上執行我們自己破解後的軟體,其的本質其實就是在app中注入我們自己編寫的動態庫,然後將app重簽名

ImageLoader 載入映象檔案

動態庫載入完成後就該載入我們自己的編寫的程式碼編譯成的二進位制檔案了,就是ImageLoaderXXXXXX系列方法.這些image內就編譯著我們自己寫的符號、程式碼等.

_objc_init

runtime的初始化

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_2_images, load_images, unmap_image);
}

複製程式碼

load

我們先關注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();
}
複製程式碼

方法中呼叫了prepare_load_methods

void prepare_load_methods(const headerType *mhdr)
{
    size_t count, i;

    runtimeLock.assertWriting();

    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        schedule_class_load(remapClass(classlist[i]));//將所有實現了+load方法的類加入到一個靜態陣列 loadable_classes
    }

    category_t **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
        realizeClass(cls);
        assert(cls->ISA()->isRealized());
        add_category_to_loadable_list(cat);//將所有實現+load方法的分類加入到一個靜態陣列 loadable_categories
    }
}
複製程式碼

_getObjc2NonlazyCategoryList 獲取所有實現了+load的分類(非懶載入的分類),然後判斷分類所對應的類是否為nil,如果分類所對應的類為nil則跳過,反之初始化分類所對應的類,然後將分類加入一個靜態陣列

我們看到了有呼叫schedule_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);//yty 將父類放在前面

    add_class_to_loadable_list(cls);
    cls->setInfo(RW_LOADED); 
}
複製程式碼

從方法中我們可以看出,這個方法是將實現了+load的方法的類加入到一個靜態陣列中,並優先呼叫父類
然後呼叫call_load_methods方法,從方法名上看應該是呼叫load方法
在這,我問大家一個問題,當一個類實現了+load方法並且分類也實現了+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_load_methods方法的實現,從程式碼中我們可以看出來,在這個方法中系統會把分類的+load方法與類本身的+load方法都呼叫了,並且類的+load要比分類中先呼叫。
那麼如果有多個分類都實現了+load,先呼叫哪個分類呢?這個和編譯有關,編譯時誰在前面誰先呼叫

load與initialize

現在我們在去看看map_2_images方法

void
map_2_images(unsigned count, const char * const paths[],
             const struct mach_header * const mhdrs[])
{
    recursive_mutex_locker_t lock(loadMethodLock);
    map_images_nolock(count, paths, mhdrs);
}
複製程式碼

發現會呼叫map_images_nolock

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    static bool firstTime = YES;
    header_info *hList[mhCount];
    uint32_t hCount;
    size_t selrefCount = 0;
   ....
   if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
}
複製程式碼

map_images_nolock忽略了部分程式碼

header_info *hList[mhCount];==>類資訊讀取到header_info的連結串列陣列
preopt_init==>優化共享快取的初始化
sel_init==>初始化方法列表
arr_init==>初始化自動釋放池+雜湊表

map_images_nolock呼叫了_read_images,

void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    header_info *hi;
    uint32_t hIndex;
    size_t count;
    size_t i;
    Class *resolvedFutureClasses = nil;
    size_t resolvedFutureClassCount = 0;
    static bool doneOnce;
    TimeLogger ts(PrintImageTimes);

    runtimeLock.assertWriting();
  for (EACH_HEADER) {
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
            // hack for class __ARCLite__, which didn't get this above
#if TARGET_OS_SIMULATOR
            if (cls->cache._buckets == (void*)&_objc_empty_cache  &&  
                (cls->cache._mask  ||  cls->cache._occupied)) 
            {
                cls->cache._mask = 0;
                cls->cache._occupied = 0;
            }
            if (cls->ISA()->cache._buckets == (void*)&_objc_empty_cache  &&  
                (cls->ISA()->cache._mask  ||  cls->ISA()->cache._occupied)) 
            {
                cls->ISA()->cache._mask = 0;
                cls->ISA()->cache._occupied = 0;
            }
#endif

            realizeClass(cls);
			objc_class *yty_cls = (objc_class*)cls;
			class_rw_t *yty_rw_t =  yty_cls->data();
			const class_ro_t *yty_ro_t =  yty_rw_t->ro;
			printf("Class: %s \n",yty_ro_t->name);
        }
    }

 // Discover categories. 
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
			printf("Category: %s \n",cat->name); //yty fax
            if (!cls) {
                // Category's target class is missing (probably weak-linked).
                // Disavow any knowledge of this category.
                catlist[i] = nil;
                if (PrintConnecting) {
                    _objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
                                 "missing weak-linked target class", 
                                 cat->name, cat);
                }
                continue;
			}

            // Process this category. 
            // First, register the category with its target class. 
            // Then, rebuild the class's method lists (etc) if 
            // the class is realized. 
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                addUnattachedCategoryForClass(cat, cls, hi);
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category -%s(%s) %s", 
                                 cls->nameForLogging(), cat->name, 
                                 classExists ? "on existing class" : "");
                }
            }

            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
                if (PrintConnecting) {
                    _objc_inform("CLASS: found category +%s(%s)", 
                                 cls->nameForLogging(), cat->name);
                }
            }
        }
    }
}
複製程式碼

因為_read_image方法太長,只擷取部分

GETSECT(_getObjc2ClassList,           classref_t,      "__objc_classlist"); //獲取當前前註冊的所有類
GETSECT(_getObjc2NonlazyClassList,    classref_t,      "__objc_nlclslist");//獲取所有非懶載入的類(實現了+load)
GETSECT(_getObjc2CategoryList,        category_t *,    "__objc_catlist");//獲取當前註冊的所有分類
GETSECT(_getObjc2NonlazyCategoryList, category_t *,    "__objc_nlcatlist");//獲取非懶載入的分類(實現了+load)
複製程式碼

在上面可以看到,非懶載入的(實現+load)的方法的類/分類就被初始化到記憶體中了,而initialize的方法則是在第一次使用了類才會呼叫

initialize

上文中我們已經說到initialize只有在類第一次呼叫的時候才會被呼叫

load與initialize
檢視initialize被呼叫時的呼叫棧

0 +[XXObject initialize]
1 _class_initialize
2 lookUpImpOrForward
3 _class_lookupMethodAndLoadCache3
4 objc_msgSend
複製程式碼

我們去看下lookUpImpOrForward的實現

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    Class curClass;
    IMP imp = nil;
    Method meth;
    bool triedResolver = NO;

    runtimeLock.assertUnlocked();

    // Optimistic cache lookup
    if (cache) {
        imp = cache_getImp(cls, sel);
        if (imp) return imp;
    }

    if (!cls->isRealized()) {
        rwlock_writer_t lock(runtimeLock);
        realizeClass(cls);
    }

    if (initialize  &&  !cls->isInitialized()) {
        _class_initialize (_class_getNonMetaClass(cls, inst));
        // 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
    }
}
複製程式碼

可以看出initialize && !cls->isInitialized()滿足的情況下會呼叫_class_initialize,initialize傳值進來是true

    bool isInitialized() {
        return getMeta()->data()->flags & RW_INITIALIZED;
    }
複製程式碼

isInitialized()是判斷當前類是否初始化,儲存在元類中
繼續看下_class_initialize方法

void _class_initialize(Class cls)
{
    assert(!cls->isMetaClass());

    Class supercls;
    bool reallyInitialize = NO;

    // Make sure super is done initializing BEFORE beginning to initialize cls.
    // See note about deadlock above.
    supercls = cls->superclass;
	//supercls->isInitialized() 判斷是否呼叫過initialize getMeta()->data()->flags & RW_INITIALIZED;在元類的 class_rw_t結構體的flags中儲存
	//1,強制優先呼叫父類initialize方法
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
    // Try to atomically set CLS_INITIALIZING.
    {
        monitor_locker_t lock(classInitLock);
		//2,如果類沒有呼叫過initialize方法或者沒有正在呼叫initialize方法 設定標誌位
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }
    
    if (reallyInitialize) {
		//3,成功設定標誌位後,向當前類傳送 +initialize 訊息
        if (PrintInitializing) {
            _objc_inform("INITIALIZE: calling +[%s initialize]",
                         cls->nameForLogging());
        }
        @try {
            callInitialize(cls);

            if (PrintInitializing) {
                _objc_inform("INITIALIZE: finished +[%s initialize]",
                             cls->nameForLogging());
            }
        }
        @catch (...) {
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: +[%s initialize] threw an exception",
                             cls->nameForLogging());
            }
            @throw;
        }
        @finally {

			// 4. 完成初始化,如果父類已經初始化完成,設定 RW_INITIALIZED 標誌位,
			//    否則,在父類初始化完成之後再設定標誌位。
            if (!supercls  ||  supercls->isInitialized()) {
                _finishInitializing(cls, supercls);
            } else {
                _finishInitializingAfter(cls, supercls);
            }
        }
        return;
    }
    
    else if (cls->isInitializing()) {
		// 5. 當前執行緒正在初始化當前類,直接返回,否則,會等待其它執行緒初始化結束後,再返回
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else {
            waitForInitializeToComplete(cls);
            return;
        }
    }
    
    else if (cls->isInitialized()) {
		// 6. 初始化成功後,直接返回
        return;
    }
    
    else {
        // We shouldn't be here. 
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}
複製程式碼

1,首先強制把父類初始化

    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
複製程式碼

2,如果類沒有呼叫過initialize方法或者沒有正在呼叫initialize方法 設定標誌位

        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }

複製程式碼

3,成功設定標誌位後,向當前類傳送 +initialize 訊息

 callInitialize(cls);
 void callInitialize(Class cls)
{
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
}
複製程式碼

這個地方我們可以看到SEL_initialize是使用objc_msgSend直接呼叫的,所以如果分類也有initialize方法,只會呼叫分類的initialize方法 4,完成初始化,如果父類已經初始化完成,設定 RW_INITIALIZED 標誌位, 否則,在父類初始化完成之後再設定標誌位。

      if (!supercls  ||  supercls->isInitialized()) {
                _finishInitializing(cls, supercls);
            } else {
                _finishInitializingAfter(cls, supercls);
            }
複製程式碼

5,當前執行緒正在初始化當前類,直接返回,否則,會等待其它執行緒初始化結束後,再返回

  if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else {
            waitForInitializeToComplete(cls);
            return;
        }
複製程式碼

6,初始化成功後,直接返回

if (cls->isInitialized()) {
   return;
  }
複製程式碼

總結

1,initialize 的呼叫是惰性的,它會在第一次呼叫當前類的方法時被呼叫,load會在objc_init初始化時候呼叫
2,initialize方法的呼叫與類是否已經被載入無關,如一個類中同時實現了+loadinitialize方法,則在main之前會呼叫+load,在第一次使用類的時候會呼叫initialize
3,如果分類與本類都寫了initialize,只會呼叫分類的;而如果都謝啦load方法,則會都呼叫
4,與 load 不同,initialize 方法呼叫時,所有的類都已經載入到了記憶體中

文章參考:
你真的瞭解 load 方法麼?
懶惰的 initialize 方法
iOS 程式 main 函式之前發生了什麼

相關文章