iOS進階之路 (九)類的加載

上篇文章,咱們學習了app加載時dyld的過程。dyld從start開始,遞歸初始化dyld_systemdyld_dispatchdyld_obj完成動態庫的連接;最後進入obj_initc++

可是鏡像文件在dyld中,objc_init在objc庫裏面,dyld的鏡像文件如何讀取出來映射到內存中,並以表的形式存儲起來呢?swift

一. _objc_init

/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/

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();
    // 關於線程key的綁定--好比每線程數據的析構函數
    tls_init();
    // 運行系統的C++靜態構造函數,在dyld調用咱們的靜態構造函數以前,libc會調用_objc_init(),因此咱們必須本身作
    static_init();
    // 無源碼,就是說objc的異常徹底纔有c++那一套
    lock_init();
    // 初始化異常處理系統,好比註冊異常的回調函數,來監控異常
    exception_init();
    // 僅供objc運行時使用,註冊處理程序,以便在映射、取消映射和初始化objc鏡像文件時調用
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼

1.1 environ_init():環境變量的初始化

讀取影響運行的環境變量。若是須要,還能夠打印環境變量幫助。數組

void environ_init(void) 
{
    ... 代碼省略

    // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if (PrintHelp  ||  PrintOptions) {
        if (PrintHelp) {
            _objc_inform("Objective-C runtime debugging. Set variable=YES to enable.");
            _objc_inform("OBJC_HELP: describe available environment variables");
            if (PrintOptions) {
                _objc_inform("OBJC_HELP is set");
            }
            _objc_inform("OBJC_PRINT_OPTIONS: list which options are set");
        }
        if (PrintOptions) {
            _objc_inform("OBJC_PRINT_OPTIONS is set");
        }

        for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];            
            if (PrintHelp) _objc_inform("%s: %s", opt->env, opt->help);
            if (PrintOptions && *opt->var) _objc_inform("%s is set", opt->env);
        }
    }
}
複製代碼

for循環代碼是在知足必定條件下,打印環境變量內容、環境變量的註釋、環境變量是否已設置等信息。緩存

  • OBJC_PRINT_LOAD_METHODS 能夠監控全部的+load方法,從而處理啓動優化
  • OBJC_DISABLE_NONPOINTER_ISA 能夠控制isa優化開關,從而優化整個內存結構
  • 更多環境變量請終端輸出export OBJC_HELP=1 查看
  • 另外,能夠在Edit scheme -> Run -> Arguments 中設置環境變量。

1.2 tls_init:線程綁定

tls_init()是關於線程key的綁定,好比線程數據的析構函數,安全

void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
    _objc_pthread_key = TLS_DIRECT_KEY;
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
複製代碼

1.3 static_init():調用構造函數

static_init()方法會運行C++靜態構造函數(只會運行系統級別的構造函數)。在dyld調用咱們的靜態構造函數以前,libc會調用_objc_init() ,所以咱們必須本身作。bash

/***********************************************************************
* static_init
* Run C++ static constructor functions.
* libc calls _objc_init() before dyld would call our static constructors, 
* so we have to do it ourselves.
**********************************************************************/
static void static_init()
{
    size_t count;
    auto inits = getLibobjcInitializers(&_mh_dylib_header, &count);
    for (size_t i = 0; i < count; i++) {
        inits[i]();
    }
}
複製代碼

1.4 lock_init

lock_init()方法是個空實現,有多是工廠重寫/接口/預留/未開源,就是說objc的異常是徹底採用C++那一套app

void lock_init(void)
{
}
複製代碼

1.5 exception_init:異常回調系統初始化

exception_init()初始化libobjc的異常回調系統,好比咱們後面會講的能夠註冊異常的毀掉函數,從而監控異常的處理。ide

void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}
複製代碼

調用只聲明不實現不做任何處理的方法,就會報錯,來到_objc_terminate。函數

1.6 _dyld_objc_notify_register()

加載全部類的信息時候咱們就要依賴這個註冊函數的回調通知告訴dyld作了哪些事情,以及須要哪些環境,以及objc和dyld之間的通信,還就是當調用函數時候,系統執行的操做,以及當沒有映射到的時候,系統應該如何操做。oop

//
// Note: only for use by objc runtime
// Register handlers to be called when objc images are mapped, unmapped, and initialized.
// Dyld will call back the "mapped" function with an array of images that contain an objc-image-info section.
// Those images that are dylibs will have the ref-counts automatically bumped, so objc will no longer need to
// call dlopen() on them to keep them from being unloaded.  During the call to _dyld_objc_notify_register(),
// dyld will call the "mapped" function with already loaded objc images.  During any later dlopen() call,
// dyld will also call the "mapped" function.  Dyld will call the "init" function when dyld would be called
// initializers in that image.  This is when objc calls any +load methods in that image.
//
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

複製代碼
  1. 僅供objc運行時使用。
  2. 註冊處理程序,以便在映射、取消映射和初始化objc圖像時調用;
  3. dyld將會經過一個包含objc-image-info的鏡像文件的數組,回調mapped函數;
  • map_images:dyld將image加載進內存時,會觸發該函數。
  • load_image:dyld初始化image時,會觸發該函數。
  • unmap_image:dyld將image移除時,會觸發該函數。

二. map_images:加載鏡像文件

dyld將image加載進內存時,會觸發該函數就,觸發_dyld_objc_notify_register回掉。

2.1 map_images

* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    mutex_locker_t lock(runtimeLock);
    return map_images_nolock(count, paths, mhdrs);
}
複製代碼

· hCount:鏡像文件的個數

2.2 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;

    // Perform first-time initialization if necessary.
    // This function is called before ordinary library initializers. 
    // fixme defer initialization until an objc-using image is found?
    if (firstTime) {
        preopt_init();
    }

    if (PrintImages) {
        _objc_inform("IMAGES: processing %u newly-mapped images...\n", mhCount);
    }


    // Find all images with Objective-C metadata.
    hCount = 0;

    // Count classes. Size various table based on the total.
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    {
        uint32_t i = mhCount;
        while (i--) {
            const headerType *mhdr = (const headerType *)mhdrs[i];

            auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
            if (!hi) {
                // no objc data in this entry
                continue;
            }
            
            if (mhdr->filetype == MH_EXECUTE) {
                // Size some data structures based on main executable`s size
#if __OBJC2__
                size_t count;
                _getObjc2SelectorRefs(hi, &count);
                selrefCount += count;
                _getObjc2MessageRefs(hi, &count);
                selrefCount += count;
#else
                _getObjcSelectorRefs(hi, &selrefCount);
#endif
                
#if SUPPORT_GC_COMPAT
                // Halt if this is a GC app.
                if (shouldRejectGCApp(hi)) {
                    _objc_fatal_with_reason
                        (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                         OS_REASON_FLAG_CONSISTENT_FAILURE, 
                         "Objective-C garbage collection " 
                         "is no longer supported.");
                }
#endif
            }
            
            hList[hCount++] = hi;
            
            if (PrintImages) {
                _objc_inform("IMAGES: loading image for %s%s%s%s%s\n", 
                             hi->fname(),
                             mhdr->filetype == MH_BUNDLE ? " (bundle)" : "",
                             hi->info()->isReplacement() ? " (replacement)" : "",
                             hi->info()->hasCategoryClassProperties() ? " (has class properties)" : "",
                             hi->info()->optimizedByDyld()?" (preoptimized)":"");
            }
        }
    }

    // Perform one-time runtime initialization that must be deferred until 
    // the executable itself is found. This needs to be done before 
    // further initialization.
    // (The executable may not be present in this infoList if the 
    // executable does not contain Objective-C code but Objective-C 
    // is dynamically loaded later.
    if (firstTime) {
        sel_init(selrefCount);
        arr_init();

#if SUPPORT_GC_COMPAT
        // Reject any GC images linked to the main executable.
        // We already rejected the app itself above.
        // Images loaded after launch will be rejected by dyld.

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE  &&  shouldRejectGCImage(mh)) {
                _objc_fatal_with_reason
                    (OBJC_EXIT_REASON_GC_NOT_SUPPORTED, 
                     OS_REASON_FLAG_CONSISTENT_FAILURE, 
                     "%s requires Objective-C garbage collection "
                     "which is no longer supported.", hi->fname());
            }
        }
#endif

#if TARGET_OS_OSX
        // Disable +initialize fork safety if the app is too old (< 10.13).
        // Disable +initialize fork safety if the app has a
        //   __DATA,__objc_fork_ok section.

        if (dyld_get_program_sdk_version() < DYLD_MACOSX_VERSION_10_13) {
            DisableInitializeForkSafety = true;
            if (PrintInitializing) {
                _objc_inform("INITIALIZE: disabling +initialize fork "
                             "safety enforcement because the app is "
                             "too old (SDK version " SDK_FORMAT ")",
                             FORMAT_SDK(dyld_get_program_sdk_version()));
            }
        }

        for (uint32_t i = 0; i < hCount; i++) {
            auto hi = hList[i];
            auto mh = hi->mhdr();
            if (mh->filetype != MH_EXECUTE) continue;
            unsigned long size;
            if (getsectiondata(hi->mhdr(), "__DATA", "__objc_fork_ok", &size)) {
                DisableInitializeForkSafety = true;
                if (PrintInitializing) {
                    _objc_inform("INITIALIZE: disabling +initialize fork "
                                 "safety enforcement because the app has "
                                 "a __DATA,__objc_fork_ok section");
                }
            }
            break;  // assume only one MH_EXECUTE image
        }
#endif

    }

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
}
複製代碼

又是很是長的一段源碼,我的閱讀源碼的時候有幾個小tips:

  • 過濾掉 容錯判斷、非空判斷、版本控制、打印 的代碼
  • 尋着函數傳入參數找線索
  • 核心代碼通常在 if-else 或者 while循環
  • 經過函數的返回值,反向尋找核心代碼
  • 蘋果的英文註釋寫的很是詳細,有些時候看註釋比看代碼效率。

上段代碼能夠縮減爲:while循環內都在操做hCount,_read_images()是核心代碼。

if (hCount > 0) {
    _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
複製代碼

三. read_images:讀取鏡像文件

源碼有400行,用上面的小tips分析縮減代碼:

void _read_images {
    
    // 1. 第一次進來 - 開始建立表 
    // gdb_objc_realized_classes : 全部類的表 - 包括實現的和沒有實現的
    // allocatedClasses: 包含用objc_allocateClassPair分配的全部類(和元類)的表。(已分配)
    
    if (!doneOnce) {
           doneOnce = YES;
        // namedClasses
        // Preoptimized classes don`t go in this table.
        // 4/3 is NXMapTable`s load factor
        int namedClassesSize =
            (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
    }
    
    // 2. 讀取全部類的列表
    for (EACH_HEADER) {
        classref_t *classlist = _getObjc2ClassList(hi, &count);
    }
    
    // 3. 獲取全部的類引用
    for (EACH_HEADER) {
        Class *classrefs = _getObjc2ClassRefs(hi, &count);
    }

    // 4. sel - 方法編號
    for (EACH_HEADER) {
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
    }
    
    // 5. 修復舊的objc_msgSend_fixup調用致使一些消息沒有處理
    for (EACH_HEADER) {
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
    }
    
    // 6. 協議
    for (EACH_HEADER) {
        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, isPreoptimized, isBundle);
        }
    }
    
    // 7. 修復協議重映射
    // 獲取全部的協議引用
    for (EACH_HEADER) {
       protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
       for (i = 0; i < count; i++) {
           remapProtocolRef(&protolist[i]);
       }
    }
    
    // 8. 實現非惰性類(用於+ load方法和靜態實例)
     for (EACH_HEADER) {
         classref_t *classlist = _getObjc2NonlazyClassList(hi, &count);
     }
    
    // 9. 在CF基礎上,實現將來類
    if (resolvedFutureClasses) {
        for (i = 0; i < resolvedFutureClassCount; i++) {
            Class cls = resolvedFutureClasses[i];
            if (cls->isSwiftStable()) {
                _objc_fatal("Swift class is not allowed to be future");
            }
            realizeClassWithoutSwift(cls);
            cls->setInstancesRequireRawIsa(false/*inherited*/);
        }
        free(resolvedFutureClasses);
    }
    
    // 10. 分類
    for (EACH_HEADER) {
        category_t **catlist = _getObjc2CategoryList(hi, &count);
    } 
}
複製代碼

3.1 建立表

實例化存儲類的哈希表,而且根據當前類數量作動態擴容

第一次進來,建立兩張表:gdb_objc_realized_classesallocatedClasses

// This is a misnomer: gdb_objc_realized_classes is actually a list of 
// named classes not in the dyld shared cache, whether realized or not.
NXMapTable *gdb_objc_realized_classes;  // exported for debuggers in objc-gdb.h
複製代碼
/
* allocatedClasses
* A table of all classes (and metaclasses) which have been allocated
* with objc_allocateClassPair.
/
static NXHashTable *allocatedClasses = nil;
複製代碼
  1. gdb_objc_realized_classes
  • 全部類的表 (包括實現的和沒有實現的)
  • 存儲不在共享緩存且已命名的全部類,其容量是類數量的4/3
  1. allocatedClasses
  • 包含用objc_allocateClassPair分配的全部類(和元類)的表(已分配)。
  • 存儲已經初始化的類。

3.2 類的重映射

從編譯後的類列表中取出全部類,遍歷進行處理

// Discover classes. Fix up unresolved future classes. Mark bundle classes.
for (EACH_HEADER) {
    // 從編譯後的類列表中取出全部類,獲取到的是一個classref_t類型的指針
    classref_t *classlist = _getObjc2ClassList(hi, &count);
    
    if (! mustReadClasses(hi)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }

    bool headerIsBundle = hi->isBundle();
    bool headerIsPreoptimized = hi->isPreoptimized();
    
    for (i = 0; i < count; i++) {
         // 數組中會取出OS_dispatch_queue_concurrent、OS_xpc_object、NSRunloop等系統類,例如CF、Fundation、libdispatch中的類。以及本身建立的類。
        Class cls = (Class)classlist[i];
        
        // 經過readClass函數獲取處理後的新類,
        Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);

        // 初始化全部懶加載的類須要的內存空間 - 如今數據沒有加載到的 - 連類都沒有初始化的
        if (newCls != cls  &&  newCls) {
            // Class was moved but not deleted. Currently this occurs
            // only when the new class resolved a future class.
            // Non-lazily realize the class below.

            // 將懶加載的類添加到數組中
            resolvedFutureClasses = (Class *)
                realloc(resolvedFutureClasses,
                        (resolvedFutureClassCount+1) * sizeof(Class));
            resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
        }
    }
}
複製代碼

readClass 核心內容一

來到本文的第一個核心內容:readClass -- 獲取處理後的新類

/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be: 
* - cls
* - nil  (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by 
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    ... 代碼省略
    return cls;
}
複製代碼

根據小tips中的經過函數的返回值,反向尋找核心代碼,readClass的做用就是返回一個cls

根據英文註釋這個Class多是有三種類型:nil / popFutureNamedClass / cls

  1. nil

當前類的父類中有類是weak-linked的,而且已經missing的,則 cls 的全部信息也是不可信的, 因此將其添加到重映射表裏,映射爲nil。

if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        return nil;
    }
複製代碼
  1. popFutureNamedClass:

正經常狀況下不會走進popFutureNamedClass判斷,這是專門針對將來的待處理的類的特殊操做,所以read_image不會對ro、rw進行操做(可打斷點調試,建立類和系統類都不會進入)

// 只有在將來要處理的類才處理,測試方法是在裏面打個斷點,看看能不能進入到裏面去,最終測試是沒進去
if (Class newCls = popFutureNamedClass(mangledName)) {
        // This name was previously allocated as a future class.
        // Copy objc_class to future class`s struct.
        // Preserve future`s rw data block.
        
        if (newCls->isAnySwift()) {
            _objc_fatal("Can`t complete future class request for '%s' "
                        "because the real class is too big.", 
                        cls->nameForLogging());
        }
        class_rw_t *rw = newCls->data();
        const class_ro_t *old_ro = rw->ro;
        memcpy(newCls, cls, sizeof(objc_class));
        rw->ro = (class_ro_t *)newCls->data();
        newCls->setData(rw);
        freeIfMutable((char *)old_ro->name);
        free((void *)old_ro);
        
        addRemappedClass(cls, newCls);
        
        replacing = cls;
        cls = newCls;
    }
複製代碼
  1. cls

調用add

if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn`t work because of duplicates
        // assert(cls == getClass(name));
        assert(getClassExceptSomeSwift(mangledName));
    } else {
        addNamedClass(cls, mangledName, replacing);
        addClassTableEntry(cls);
    }
複製代碼
  • 將當前類添加到已建立好的gdb_objc_realized_classes哈希表(存放全部類)
/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    runtimeLock.assertLocked();
    Class old;
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);

        // getMaybeUnrealizedNonMetaClass uses name lookups.
        // Classes not found by name lookup must be in the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else {
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
    assert(!(cls->data()->flags & RO_META));

    // wrong: constructed classes are already realized when they get here
    // assert(!cls->isRealized());
}
複製代碼
  • 當前類已經初始化,因此要添加到allocatedClasses哈希表(已初始化)
/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void addClassTableEntry(Class cls, bool addMeta = true) {
    runtimeLock.assertLocked();

    // This class is allowed to be a known class via the shared cache or via
    // data segments, but it is not allowed to be in the dynamic table already.
    assert(!NXHashMember(allocatedClasses, cls));

    if (!isKnownClass(cls))
        NXHashInsert(allocatedClasses, cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}
複製代碼

readClass主要做用:將當前類加入已經建立好的到gdb_objc_realized_classes(存放全部類) 和 allocatedClasses(已初始化)兩張表中

3.3 修復重映射

將未映射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]);
        }
        // fixme why doesn`t test future1 catch the absence of this?
        classrefs = _getObjc2SuperRefs(hi, &count);
        for (i = 0; i < count; i++) {
            remapClassRef(&classrefs[i]);
        }
    }
}
複製代碼

3.4 添加SEL

將全部SEL都註冊到namedSelectors哈希表中,是另一張哈希表

// Fix up @selector references
static size_t UnfixedSelectors;
{
    mutex_locker_t lock(selLock);
    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);
        }
    }
}
複製代碼

3.5 修復舊的函數指針調用遺留

for (EACH_HEADER) {
    message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
    if (count == 0) continue;

    if (PrintVtables) {
        _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                     "call sites in %s", count, hi->fname());
    }
    for (i = 0; i < count; i++) {
         // 內部將經常使用的alloc、objc_msgSend等函數指針進行註冊,並fix爲新的函數指針
        fixupMessageRef(refs+i);
    }
}
複製代碼

3.6 添加協議

遍歷全部協議列表,而且將協議列表加載到Protocol的哈希表中

// Discover protocols. Fix up protocol refs.
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);
    }
}

複製代碼

3.7 修復協議表引用

修復協議列表引用,優化後的images多是正確的,可是並不肯定

// Fix up @protocol references
// Preoptimized images may have the right 
// answer already but we don`t know for sure.
for (EACH_HEADER) {
    // 須要注意到是,下面的函數是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不同
    protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
    for (i = 0; i < count; i++) {
        remapProtocolRef(&protolist[i]);
    }
}

複製代碼

3.8 實現非懶加載類 核心內容二

NonlazyClass is all about a class implementing or not a +load method.

根據蘋果文檔,實現了+ load方法的類是非懶加載類,不然就是懶加載類。

  • 非懶加載類:+ load方法是在main函數以前被調用的。這個時候爲了能後保證+ load方法能被調用,就必須提早把這個類加載好。
  • 懶加載:顧名思義,是平時不會被加載,只有在用到的時候纔會被加載。

全部非懶加載類如何初始化,進行rw、ro等操做的?

// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
    // 1. 取出非懶加載類
    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
        // 2. 將類加入到表裏,若是此類已經添加過,則再也不添加
        addClassTableEntry(cls);
        
        if (cls->isSwiftStable()) {
            if (cls->swiftMetadataInitializer()) {
                _objc_fatal("Swift class %s with a metadata initializer "
                            "is not allowed to be non-lazy",
                            cls->nameForLogging());
            }
            // fixme also disallow relocatable classes
            // We can`t disallow all Swift classes because of
            // classes like Swift.__EmptyArrayStorage
        }
        // 3. 實現全部非懶加載的類(實例化類對象的一些信息,例如rw)
        realizeClassWithoutSwift(cls);
    }
}

複製代碼
  • _getObjc2NonlazyClassList獲取到__objc_nlclslist,取出非懶加載類
  • addClassTableEntry再加載一遍——若是已添加就不會添加進去,確保整個結構都被添加
  • realizeClassWithoutSwift來到本文的第二個核心內容

realizeClassWithoutSwift

實現全部非懶加載的類(實例化類對象的一些信息,例如rw)

/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls, 
* including allocating its read-write data. 
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class. 
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls)
{
    if (!cls) return nil;   
    if (cls->isRealized()) return cls;
    
    ...代碼省略
    
    // Attach categories
    methodizeClass(cls);

    return cls;
}
複製代碼

首先確認一點,咱們是從for循環裏進入的realizeClassWithoutSwift,當前循環結束的條件是 !clscls -> isRealized()

1. 初始化rw,並將ro賦值給rw->ro

  • rw表示readWrite,因爲動態性,可能會往類中添加屬性、方法、添加協議
  • ro表示readOnly,在編譯時已經肯定了內存
ro = (const class_ro_t *)cls->data();
if (ro->flags & RO_FUTURE) {
    // This was a future class. rw data is already allocated.
    rw = cls->data();
    ro = cls->data()->ro;
    cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
    // Normal class. Allocate writeable class data.
    rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
    rw->ro = ro;
    rw->flags = RW_REALIZED|RW_REALIZING;
    cls->setData(rw);
}
複製代碼

此時只是建立rw,並將ro賦值給rw->ro。rw裏的methodspropertiesprotocols 仍然爲空。

2. 完善類結構

  • 獲取當前類父類元類
  • 若是有父類,就經過addSubclass當前類連接到超類的子類列表中去

不斷的遞歸操做,完善類的繼承鏈,遞歸結束的條件就是上文提到的if (!cls) return nil; (根元類 -> NSObject -> nil)

supercls = realizeClassWithoutSwift(remapClass(cls->superclass)); // 遞歸獲取父類
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));       // 遞歸獲取元類
...
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;     
cls->initClassIsa(metacls);         // 父類與元類的歸屬關係
...
// Connect this class to its superclass`s subclass lists
if (supercls) {                 
    addSubclass(supercls, cls);     // 將此類連接到其超類的子類鏈表
} else {
    addRootClass(cls);
}
複製代碼

3. methodizeClass:真正對rw處理

  1. methodizeClass會從ro中讀取methodspropertiesprotocols賦值給rw
/***********************************************************************
* methodizeClass
* Fixes up cls`s method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data();  // 此時的rw中method list, protocol list, and property list仍然爲空
    auto ro = rw->ro;

    ···

    // Install methods and properties that the class implements itself.
    // 將rw->ro中的 method list, protocol list, and property list 賦值給 rw
    method_list_t *list = ro->baseMethods();
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        rw->methods.attachLists(&list, 1);
    }

    property_list_t *proplist = ro->baseProperties;
    if (proplist) {
        rw->properties.attachLists(&proplist, 1);
    }

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }
    
    ···
}
複製代碼
  1. attachLists是如何插入數據的呢?
void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;

    if (hasArray()) {
        // many lists -> many lists
        uint32_t oldCount = array()->count;//10
        uint32_t newCount = oldCount + addedCount;//4
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        array()->count = newCount;// 10+4

        memmove(array()->lists + addedCount, array()->lists,
                oldCount * sizeof(array()->lists[0]));
        
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        list = addedLists[0];
    } 
    else {
        // 1 list -> many lists
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        uint32_t newCount = oldCount + addedCount;
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        array()->count = newCount;
        if (oldList) array()->lists[addedCount] = oldList;
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}
複製代碼
  • 多對多:若是當前調用attachListslist_array_tt二維數組中有多個一維數組

realloc容器擴容:舊的大小 + 新增的大小

舊的數據memmove移動到容器的末尾

新的數據memcpy拷貝到容器的起始位置

  • 0對一:若是調用attachLists的list_array_tt二維數組爲空且新增大小數目爲 1

直接賦值addedList的第一個list

  • 一對多:若是當前調用attachLists的list_array_tt二維數組只有一個一維數組

realloc容器擴容:舊的大小 + 新增的大小

因爲只有一個一維數組,舊數據直接賦值到新容器的末尾

新的數據memcpy拷貝到容器的起始位置

  1. 問題1:memmovememcpy有什麼區別,爲何要先memmovememcpy
  • 老數據與新數據是在同一片內存塊中,不方便整段copy,,須要memmove進行內存平移,保證內存結構安全
  • memcpy從原內存地址的起始位置開始拷貝若干個字節到目標內存地址中,速度快。
  1. 問題2:爲何分類的方法會覆蓋類的同名方法?
  • 分類的方法被memcpy在rw的method的起始位置,不是覆蓋,而在消息查找環節更早被找到。
  1. 問題3:既然有了ro,爲何還要有rw呢?
  • ro表示readOnly,屬於類的自己數據,最基礎的數據。在編譯時已經肯定了內容,不會被改變
  • rw表示readWrite,因爲OC的動態性,可能會往類中添加屬性、方法、添加協議。
  1. 問題4:除了在加載類的時候會調用attachLists,還有哪些狀況
  • methodizeClass:類的加載 - 處理方法/屬性/協議
  • addMethods:添加方法
  • _class_addProperty:添加屬性
  • _class_addProtocol:添加協議
  • attachCategories:添加分類

3.9 懶加載類的加載

3.10 分類的加載

咱們下篇文章會學習懶加載類和分類`的加載。

四.總結

本文主要講了dyld的鏡像文件經過_dyld_objc_notify_register(&map_images, load_images, unmap_image);讀取出來映射到內存中,並以表的形式存儲起來的過程。

  • map_images:dyld將image加載進內存時,會觸發該函數。
  • load_image:dyld初始化image時,會觸發該函數。
  • unmap_image:dyld將image移除時,會觸發該函數。

非懶加載類加載流程:

  • map_image -> read_images - realizeClassWithoutSwift - methodlizeClass -> attachLists對rw賦值。
相關文章
相關標籤/搜索