探祕Runtime - Runtime加載過程

該文章屬於<簡書 — 劉小壯>原創,轉載請註明:

<簡書 — 劉小壯> https://www.jianshu.com/p/4fb2d7014e9egit


博客配圖


程序加載過程

在iOS程序中會用到不少系統的動態庫,這些動態庫都是動態加載的。全部iOS程序共用一套系統動態庫,在程序開始運行時纔會開始連接動態庫。github

the dynamic link editor

除了在項目設置裏顯式出現的動態庫外,還會有一些隱式存在的動態庫。例如objcRuntime所屬的libobjc.dyldlibSystem.dyld,在libSystem中包含經常使用的libdispatch(GCD)libsystem_c(C語言基礎庫)、libsystem_blocks(Block)等。數組

使用動態庫的優勢:app

  1. 防止重複。iOS系統中全部App公用一套系統動態庫,防止重複的內存佔用。
  2. 減小包體積。由於系統動態庫被內置到iOS系統中,因此打包時不須要把這部分代碼打進去,能夠減少包體積。
  3. 動態性。由於系統動態庫是動態加載的,因此能夠在更新系統後,將動態庫換成新的動態庫。

加載過程

在應用程序啓動後,由dyld(the dynamic link editor)進行程序的初始化操做。大概流程就像下面列出的步驟,其中第三、四、5步會執行屢次,在ImageLoader加載新的image進內存後就會執行一次。函數

  1. 在引用程序啓動後,由dyld將應用程序加載到二進制中,並完成一些文件的初始化操做。
  2. Runtimedyld中註冊回調函數。
  3. 經過ImageLoader將全部image加載到內存中。
  4. dyldimage發生改變時,主動調用回調函數。
  5. Runtime接收到dyld的函數回調,開始執行map_imagesload_images等操做,並回調+load方法。
  6. 調用main()函數,開始執行業務代碼。

ImageLoaderimage的加載器,image能夠理解爲編譯後的二進制。oop

下面是在Runtimemap_images函數打斷點,觀察回調狀況的彙編代碼。能夠看出,調用是由dyld發起的,由ImageLoader通知dyld進行調用。源碼分析

彙編調用

關於dyld我並無深刻研究,有興趣的同窗能夠到Github上下載源碼研究一下。佈局

動態加載

一個OC程序能夠在運行過程當中動態加載和連接新類或Category,新類或Category會加載到程序中,其處理方式和其餘類是相同的。動態加載還能夠作許多不一樣的事,動態加載容許應用程序進行自定義處理。優化

OC提供了objc_loadModules運行時函數,執行Mach-O中模塊的動態加載,在上層NSBundle對象提供了更簡單的訪問APIui

map images

Runtime加載時,會調用_objc_init函數,並在內部註冊三個函數指針。其中map_images函數是初始化的關鍵,內部完成了大量Runtime環境的初始化操做。

map_images函數中,內部也是作了一個調用中轉。而後調用到map_images_nolock函數,內部核心就是_read_images函數。

void _objc_init(void)
{
    // .... 各類init
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

void map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    rwlock_writer_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}

void map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
}
複製代碼

_read_images函數中完成了大量的初始化操做,函數內部代碼量比較大,下面是精簡版帶註釋的源代碼。

先總體梳理一遍_read_images函數內部的邏輯:

  1. 加載全部類到類的gdb_objc_realized_classes表中。
  2. 對全部類作重映射。
  3. 將全部SEL都註冊到namedSelectors表中。
  4. 修復函數指針遺留。
  5. 將全部Protocol都添加到protocol_map表中。
  6. 對全部Protocol作重映射。
  7. 初始化全部非懶加載的類,進行rwro等操做。
  8. 遍歷已標記的懶加載的類,並作初始化操做。
  9. 處理全部Category,包括ClassMeta Class
  10. 初始化全部未初始化的類。
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);

#define EACH_HEADER \
    hIndex = 0;         \
    hIndex < hCount && (hi = hList[hIndex]); \
    hIndex++

    if (!doneOnce) {
        doneOnce = YES;
        // 實例化存儲類的哈希表,而且根據當前類數量作動態擴容
        int namedClassesSize = 
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
    }

    // 由編譯器讀取類列表,並將全部類添加到類的哈希表中,而且標記懶加載的類並初始化內存空間
    for (EACH_HEADER) {
        if (! mustReadClasses(hi)) {
            continue;
        }

        bool headerIsBundle = hi->isBundle();
        bool headerIsPreoptimized = hi->isPreoptimized();

        /** 將新類添加到哈希表中 */
        
        // 從編譯後的類列表中取出全部類,獲取到的是一個classref_t類型的指針
        classref_t *classlist = _getObjc2ClassList(hi, &count);
        for (i = 0; i < count; i++) {
            // 數組中會取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系統類,例如CF、Fundation、libdispatch中的類。以及本身建立的類
            Class cls = (Class)classlist[i];
            // 經過readClass函數獲取處理後的新類,內部主要操做ro和rw結構體
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

            // 初始化全部懶加載的類須要的內存空間
            if (newCls != cls  &&  newCls) {
                // 將懶加載的類添加到數組中
                resolvedFutureClasses = (Class *)
                    realloc(resolvedFutureClasses, 
                            (resolvedFutureClassCount+1) * sizeof(Class));
                resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
            }
        }
    }
    
    // 將未映射Class和Super Class重映射,被remap的類都是非懶加載的類
    if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            // 重映射Class,注意是從_getObjc2ClassRefs函數中取出類的引用
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
            // 重映射父類
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }

    // 將全部SEL都註冊到哈希表中,是另一張哈希表
    static size_t UnfixedSelectors;
    sel_lock();
    for (EACH_HEADER) {
        if (hi->isPreoptimized()) continue;

        bool isBundle = hi->isBundle();
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) {
            const char *name = sel_cname(sels[i]);
            // 註冊SEL的操做
            sels[i] = sel_registerNameNoLock(name, isBundle);
        }
    }

    // 修復舊的函數指針調用遺留
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;
        for (i = 0; i < count; i++) {
            // 內部將經常使用的alloc、objc_msgSend等函數指針進行註冊,並fix爲新的函數指針
            fixupMessageRef(refs+i);
        }
    }

    // 遍歷全部協議列表,而且將協議列表加載到Protocol的哈希表中
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        // cls = Protocol類,全部協議和對象的結構體都相似,isa都對應Protocol類
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);
        // 獲取protocol哈希表
        NXMapTable *protocol_map = protocols();
        bool isPreoptimized = hi->isPreoptimized();
        bool isBundle = hi->isBundle();

        // 從編譯器中讀取並初始化Protocol
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }
    
    // 修復協議列表引用,優化後的images多是正確的,可是並不肯定
    for (EACH_HEADER) {
        // 須要注意到是,下面的函數是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不同
        protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapProtocolRef(&protolist[i]);
        }
    }

    // 實現非懶加載的類,對於load方法和靜態實例變量
    for (EACH_HEADER) {
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
            // 實現全部非懶加載的類(實例化類對象的一些信息,例如rw)
            realizeClass(cls);
        }
    }

    // 遍歷resolvedFutureClasses數組,實現全部懶加載的類
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            // 實現懶加載的類
            realizeClass(resolvedFutureClasses[i]);
            resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }

    // 發現和處理全部Category
    for (EACH_HEADER) {
        // 外部循環遍歷找到當前類,查找類對應的Category數組
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
        bool hasClassProperties = hi->info()->hasCategoryClassProperties();

        // 內部循環遍歷當前類的全部Category
        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);

            // 首先,經過其所屬的類註冊Category。若是這個類已經被實現,則從新構造類的方法列表。
            bool classExists = NO;
            if (cat->instanceMethods ||  cat->protocols  
                ||  cat->instanceProperties) 
            {
                // 將Category添加到對應Class的value中,value是Class對應的全部category數組
                addUnattachedCategoryForClass(cat, cls, hi);
                // 將Category的method、protocol、property添加到Class
                if (cls->isRealized()) {
                    remethodizeClass(cls);
                    classExists = YES;
                }
            }

            // 這塊和上面邏輯同樣,區別在於這塊是對Meta Class作操做,而上面則是對Class作操做
            // 根據下面的邏輯,從代碼的角度來講,是能夠對原類添加Category的
            if (cat->classMethods  ||  cat->protocols  
                ||  (hasClassProperties && cat->_classProperties)) 
            {
                addUnattachedCategoryForClass(cat, cls->ISA(), hi);
                if (cls->ISA()->isRealized()) {
                    remethodizeClass(cls->ISA());
                }
            }
        }
    }

    // 初始化從磁盤中加載的全部類,發現Category必須是最後執行的
    // 從runtime objc4-532版本源碼來看,DebugNonFragileIvars字段一直是-1,因此不會進入這個方法中
    if (DebugNonFragileIvars) {
        realizeAllClasses();
    }
#undef EACH_HEADER
}
複製代碼

其內部還調用了不少其餘函數,後面會詳細介紹函數內部實現。

load images

在項目中常常用到load類方法,load類方法的調用時機比main函數還要靠前。load方法是由系統來調用的,而且在整個程序運行期間,只會調用一次,因此能夠在load方法中執行一些只執行一次的操做。

通常Method Swizzling都會放在load方法中執行,這樣在執行main函數前,就能夠對類方法進行交換。能夠確保正式執行代碼時,方法確定是被交換過的。

若是對一個類添加Category後,而且重寫其原有方法,這樣會致使Category中的方法覆蓋原類的方法。可是load方法倒是例外,全部Category和原類的load方法都會被執行。

源碼分析

load方法由Runtime進行調用,下面咱們分析一下load方法的實現,load的實現源碼都在objc-loadmethod.mm中。

在一個新的工程中,咱們建立一個TestObject類,並在其load方法中打一個斷點,看一下系統的調用堆棧。

調用堆棧

從調用棧能夠看出,是經過系統的動態連接器dyld開始的調用,而後調到Runtimeload_images函數中。load_images函數是經過_dyld_objc_notify_register函數,將本身的函數指針註冊給dyld的。

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);
}
複製代碼

load_images函數中主要作了兩件事,首先經過prepare_load_methods函數準備Class load listCategory load list,而後經過call_load_methods函數調用已經準備好的兩個方法列表。

void
load_images(const char *path __unused, const struct mach_header *mh)
{
    if (!hasLoadMethods((const headerType *)mh)) return;
    prepare_load_methods((const headerType *)mh);
    call_load_methods();
}
複製代碼

首先咱們看一下prepare_load_methods函數的實現,看一下其內部是怎麼查找load方法的。能夠看到,其內部主要分爲兩部分,查找Classload方法列表和查找Category方法列表。

準備類的方法列表時,首先經過_getObjc2NonlazyClassList函數獲取全部非懶加載類的列表,這時候獲取到的是一個classref_t類型的數組,而後遍歷數組添加load方法列表。

Category過程也是相似,經過_getObjc2NonlazyCategoryList函數獲取全部非懶加載Category的列表,獲得一個category_t類型的數組,須要注意的是這是一個指向指針的指針。而後對其進行遍歷並添加到load方法列表,ClassCategoryload方法列表是兩個列表。

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

    // 獲取到非懶加載的類的列表
    classref_t *classlist = 
        _getObjc2NonlazyClassList(mhdr, &count);
    for (i = 0; i < count; i++) {
        // 設置Class的調用列表
        schedule_class_load(remapClass(classlist[i]));
    }
    
    // 獲取到非懶加載的Category列表
    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;
        // 實例化所屬的類
        realizeClass(cls);
        // 設置Category的調用列表
        add_category_to_loadable_list(cat);
    }
}
複製代碼

在添加類的load方法列表時,內部會遞歸遍歷把全部父類的load方法都添加進去,順着繼承者鏈的順序添加,會先把父類添加在前面。而後會調用add_class_to_loadable_list函數,將本身的load方法添加到對應的數組中。

static void schedule_class_load(Class cls)
{
    if (!cls) return;
    // 已經添加Class的load方法到調用列表中
    if (cls->data()->flags & RW_LOADED) return;

    // 確保super已經被添加到load列表中,默認是整個繼承者鏈的順序
    schedule_class_load(cls->superclass);
    
    // 將IMP和Class添加到調用列表
    add_class_to_loadable_list(cls);
    // 設置Class的flags,表示已經添加Class到調用列表中
    cls->setInfo(RW_LOADED); 
}
複製代碼

Category則不須要考慮父類的問題,因此直接在prepare_load_methods函數中遍歷Category數組,而後調用add_category_to_loadable_list函數便可。

add_category_to_loadable_list函數中,會判斷當前Category有沒有實現load方法,若是沒有則直接return,若是實現了則添加到loadable_categories數組中。

類的add_class_to_loadable_list函數內部實現也是相似,區別在於類的數組叫作loadable_classes

void add_category_to_loadable_list(Category cat)
{
    IMP method;

    // 獲取Category的load方法的IMP
    method = _category_getLoadMethod(cat);

    // 若是Category沒有load方法則return
    if (!method) return;
    // 若是已使用大小等於數組大小,對數組進行動態擴容
    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++;
}
複製代碼

到此爲止,loadable_classesloadable_categories兩個數組已經準備好了,load_images會調用call_load_methods函數執行這些load方法。在這個方法中,call_class_loads函數是負責調用類方法列表的,call_category_loads負責調用Category的方法列表。

void call_load_methods(void)
{
    bool more_categories;
    void *pool = objc_autoreleasePoolPush();

    do {
        // 反覆執行call_class_loads函數,直到數組中沒有可執行的load方法
        while (loadable_classes_used > 0) {
            call_class_loads();
        }
        more_categories = call_category_loads();
    } while (loadable_classes_used > 0  ||  more_categories);

    objc_autoreleasePoolPop(pool);
    loading = NO;
}
複製代碼

下面是調用類方法列表的代碼,內部主要是經過對loadable_classes數組進行遍歷,並獲取到loadable_class的結構體,結構體中存在ClassIMP,而後直接調用便可。

Category的調用方式和類的同樣,就不在下面貼代碼了。須要注意的是,load方法都是直接調用的,並無走運行時的objc_msgSend函數。

static void call_class_loads(void)
{
    int i;
    struct loadable_class *classes = loadable_classes;
    int used = loadable_classes_used;
    loadable_classes = nil;
    loadable_classes_allocated = 0;
    loadable_classes_used = 0;
    
    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; 
        (*load_method)(cls, SEL_load);
    }
    
    if (classes) free(classes);
}

struct loadable_class {
    Class cls;  // may be nil
    IMP method;
};
複製代碼

根據上面的源碼分析,咱們能夠看出load方法的調用順序應該是 「父類 -> 子類 -> 分類」 的順序。由於執行加載Class的時機是在Category以前的,並且load子類以前會先load父類,因此是這種順序。

initialize

load方法相似的也有initialize方法,initialize方法也是由Runtime進行調用的,本身不能夠直接調用。與load方法不一樣的是,initialize方法是在第一次調用類所屬的方法時,纔會調用initialize方法,而load方法是在main函數以前就所有調用了。因此理論上來講initialize可能永遠都不會執行,若是當前類的方法永遠不被調用的話。

下面咱們研究一下initializeRuntime中的源碼。

在向對象發送消息時,lookUpImpOrForward函數中會判斷當前類是否被初始化,若是沒有被初始化,則先進行初始化再調用類的方法。

IMP lookUpImpOrForward(Class cls, SEL sel, id inst, bool initialize, bool cache, bool resolver);

// ....省略好多代碼

// 第一次調用當前類的話,執行initialize的代碼
if (initialize  &&  !cls->isInitialized()) {
    _class_initialize (_class_getNonMetaClass(cls, inst));
}

// ....省略好多代碼
複製代碼

在進行初始化的時候,和load方法的調用順序同樣,會按照繼承者鏈先初始化父類。_class_initialize函數中關鍵的兩行代碼是callInitializelockAndFinishInitializing的調用。

// 第一次調用類的方法,初始化類對象
void _class_initialize(Class cls)
{
    Class supercls;
    bool reallyInitialize = NO;

    // 遞歸初始化父類。initizlize不用顯式的調用super,由於runtime已經在內部調用了
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
    {
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }
    
    if (reallyInitialize) {
        _setThisThreadIsInitializingClass(cls);

        if (MultithreadedForkChild) {
            performForkChildInitialize(cls, supercls);
            return;
        }
        @try {
            // 經過objc_msgSend()函數調用initialize方法
            callInitialize(cls);
        }
        @catch (...) {
            @throw;
        }
        @finally {
            // 執行initialize方法後,進行系統的initialize過程
            lockAndFinishInitializing(cls, supercls);
        }
        return;
    }
    
    else if (cls->isInitializing()) {
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else if (!MultithreadedForkChild) {
            waitForInitializeToComplete(cls);
            return;
        } else {
            _setThisThreadIsInitializingClass(cls);
            performForkChildInitialize(cls, supercls);
        }
    }
}
複製代碼

經過objc_msgSend函數調用initialize方法。

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

lockAndFinishInitializing函數中會完成一些初始化操做,其內部會調用_finishInitializing函數,在函數內部會調用classsetInitialized函數,核心工做都由setInitialized函數完成。

static void lockAndFinishInitializing(Class cls, Class supercls)
{
    monitor_locker_t lock(classInitLock);
    if (!supercls  ||  supercls->isInitialized()) {
        _finishInitializing(cls, supercls);
    } else {
        _finishInitializingAfter(cls, supercls);
    }
}
複製代碼

負責初始化類和元類,函數內部主要是查找當前類和元類中是否認義了某些方法,而後根據查找結果設置類和元類的一些標誌位。

void 
objc_class::setInitialized()
{
    Class metacls;
    Class cls;

    // 獲取類和元類對象
    cls = (Class)this;
    metacls = cls->ISA();

    bool inherited;
    bool metaCustomAWZ = NO;
    if (MetaclassNSObjectAWZSwizzled) {
        metaCustomAWZ = YES;
        inherited = NO;
    }
    else if (metacls == classNSObject()->ISA()) {
        // 查找是否實現了alloc和allocWithZone方法
        auto& methods = metacls->data()->methods;
        for (auto mlists = methods.beginCategoryMethodLists(), 
                  end = methods.endCategoryMethodLists(metacls); 
             mlists != end;
             ++mlists)
        {
            if (methodListImplementsAWZ(*mlists)) {
                metaCustomAWZ = YES;
                inherited = NO;
                break;
            }
        }
    }
    else if (metacls->superclass->hasCustomAWZ()) {
        metaCustomAWZ = YES;
        inherited = YES;
    } 
    else {
        auto& methods = metacls->data()->methods;
        for (auto mlists = methods.beginLists(),
                  end = methods.endLists(); 
             mlists != end;
             ++mlists)
        {
            if (methodListImplementsAWZ(*mlists)) {
                metaCustomAWZ = YES;
                inherited = NO;
                break;
            }
        }
    }
    if (!metaCustomAWZ) metacls->setHasDefaultAWZ();
    if (PrintCustomAWZ  &&  metaCustomAWZ) metacls->printCustomAWZ(inherited);

    bool clsCustomRR = NO;
    if (ClassNSObjectRRSwizzled) {
        clsCustomRR = YES;
        inherited = NO;
    }
    // 查找元類是否實現MRC方法,若是是則進入if語句中
    if (cls == classNSObject()) {
        auto& methods = cls->data()->methods;
        for (auto mlists = methods.beginCategoryMethodLists(), 
                  end = methods.endCategoryMethodLists(cls); 
             mlists != end;
             ++mlists)
        {
            if (methodListImplementsRR(*mlists)) {
                clsCustomRR = YES;
                inherited = NO;
                break;
            }
        }
    }
    else if (!cls->superclass) {
        clsCustomRR = YES;
        inherited = NO;
    } 
    else if (cls->superclass->hasCustomRR()) {
        clsCustomRR = YES;
        inherited = YES;
    } 
    else {
        // 查找類是否實現MRC方法,若是是則進入if語句中
        auto& methods = cls->data()->methods;
        for (auto mlists = methods.beginLists(), 
                  end = methods.endLists(); 
             mlists != end;
             ++mlists)
        {
            if (methodListImplementsRR(*mlists)) {
                clsCustomRR = YES;
                inherited = NO;
                break;
            }
        }
    }
    if (!clsCustomRR) cls->setHasDefaultRR();
    if (PrintCustomRR  &&  clsCustomRR) cls->printCustomRR(inherited);
    metacls->changeInfo(RW_INITIALIZED, RW_INITIALIZING);
}
複製代碼

須要注意的是,initialize方法和load方法不太同樣,Category中定義的initialize方法會覆蓋原方法而不是像load方法同樣均可以調用。


簡書因爲排版的問題,閱讀體驗並很差,佈局、圖片顯示、代碼等不少問題。因此建議到我Github上,下載Runtime PDF合集。把全部Runtime文章總計九篇,都寫在這個PDF中,並且左側有目錄,方便閱讀。

Runtime PDF

下載地址:Runtime PDF 麻煩各位大佬點個贊,謝謝!😁

相關文章
相關標籤/搜索