iOS底層學習 - 從編譯到啓動的奇幻旅程(三)

在上兩章節,咱們已經瞭解了一個App從編譯到main函數的調用,發生了什麼事情,而且咱們知道了_objc_init在加載鏡像文件時,會在dyld動態連接器中去註冊,二者以前經過此來進行通信。可是dyld加載相關鏡像文件後,這些鏡像文件是如何加載到內存當中的,是以什麼方式存在於內存當中的,這就是本章探究的核心。html

相關文章傳送門:swift

iOS底層學習 - 從編譯到啓動的奇幻旅程(二)數組

iOS底層學習 - 類的前世此生(一)bash

咱們知道dyld的主體流程就是連接動態庫和鏡像文件,那麼objc的鏡像文件自己是如何進行讀取到內存中的,咱們從源碼來解讀app

/***********************************************************************
* _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();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
複製代碼

經過源碼,咱們基本能夠得出如下結論:函數

  • _objc_initlibSystem庫調用
  • 在此方法裏進行自舉鏡像加載到內存

下面咱們逐步進行分析,objc相關類等,是如何加載的oop

準備工做

在鏡像加載以前,objc進行了一系列的準備工做,咱們來逐步分析,以下圖:post

environ_init

根據字面意思咱們能夠得出,這個方法是讀取影響運行時的環境變量,可使用 export OBJC_HELP=1 來打印環境變量,從而進行一些調試,能夠再Xcode中進行設置,從而達到想要的效果打印。相關能夠參考OBJC_HELP學習

  • OBJC_DISABLE_NONPOINTER_ISA 這個能夠設置non-pointer的ISA,ISA的值不須要和mask進行與操做,直接指向
  • OBJC_PRINT_LOAD_METHODS這個能夠打印類和分類的load方法,對咱們進行啓動優化頗有幫助。

tls_init

這個函數是關於線程Key的綁定,好比線程數據的析構函數優化

static_init

根據註釋能夠得出,這個函數主要作了以下事情

  • 運行C++靜態構造函數
  • 在dyld調用咱們靜態構造函數以前,libc會調用_objc_init(),因此必須在此前調用
/***********************************************************************
* 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]();
    }
}
複製代碼

lock_init

是一個空實現,說明objc採用的是C++的加鎖機制

exception_init

初始化 libobjc的異常處理系統,用來監控崩潰等,好比未實現的方法

_dyld_objc_notify_register

經過上一章,咱們對這個方法已經有了瞭解,這是一個dyld的註冊回調函數,從而讓dyld能夠連接加載鏡像

  • 這個函數只在運行時提供給objc使用
  • 註冊處理程序,以便在映射和取消映射和初始化objc鏡像是調用
  • dyld將使用包含objc_image_info的鏡像文件的數組,回調給mapped函數

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

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

經過map_images_nolock的源碼咱們能夠發現,若是hCount表示鏡像文件的個數,則調用_read_images函數來進行加載鏡像文件。因此加載內存確定在此

_read_images解析

因爲代碼比較長,咱們先作一個大致的歸納,而後逐步進行研究,基本處理以下:

  • 第一次加載全部類到表中

    gdb_objc_realized_classes爲全部類的表-包括實現和未實現的

    allocatedClasses包含使用objc_allocateClassPair分配的全部類(元類)的表

  • 對全部的類作重映射

  • 將全部的SEL註冊到namedSelector表中

  • 修復舊的objc_msgSend_fixup調用致使一些消息沒有處理

  • 將全部的Protocol都添加到protocol_map表中

  • 對全部的Protocol作重映射,獲取到引用

  • 初始化全部非懶加載的類,進行rwro等操做

  • 遍歷已經標記的懶加載的類,並作相應的初始化

  • 處理全部的Category,包括類和元類

  • 初始化全部未初始化的類

下面咱們主要對類的加載來進行重點的分析

doneOnce

變量doneOnce表示這個操做只進行一次,由於是建立表的操做,因此只須要一次建立便可,主要的代碼以下

if (!doneOnce) {
        doneOnce = YES;
        ...
        initializeTaggedPointerObfuscator();
        // 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);
        
        ts.log("IMAGE TIMES: first time tasks");
    }

複製代碼

類的重映射

類的重映射相關代碼以下。主要是從列表中遍歷出類,並進行處理和添加到相對應的表中

// 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函數獲取處理後的新類,下面具體分析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是如何處理類的?

咱們能夠看到有以下圖的代碼,裏面對clsrw等進行了處理,咱們知道rw裏存了類的方法等,因此是否是在這裏處理的呢?

可是咱們在方法裏打斷點,發現並無執行,說明咱們建立的類和系統方法的類都沒有走這個方法,因此類的rw數據填充並非在此

經過紅框中的判斷,咱們可得,這個判斷條件是處理專門針對將來的待處理的類的特殊操做

那麼繼續向下能夠看到以下代碼,能夠看到主要是執行了addNamedClassaddClassTableEntry兩個函數

if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert does not work because of duplicates
        // assert(cls == getClass(name));
        assert(getClassExceptSomeSwift(mangledName));
    } else {
        addNamedClass(cls, mangledName, replacing);
        addClassTableEntry(cls);
}
複製代碼

查看addNamedClass相關代碼,主要將類添加到底層總的哈希表中

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

查看addClassTableEntry相關代碼,由於當前類已經有了地址,進行了初始化,因此也要添加到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);
}
複製代碼

至此,初始化類已經添加到兩張表中了

SEL添加到表中

SEL相關代碼的處理以下,主要也是一個表寫入的操做,寫入了namedSelector表中,和類並非一張表

//✅ 將全部SEL都註冊到哈希表中,是另一張哈希表
    // 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);
            }
        }
    }

複製代碼

將全部的Protocol都添加到protocol_map表中

相關代碼以下。

// Discover protocols. Fix up protocol refs.
    //✅ 遍歷全部協議列表,而且將協議列表加載到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);
        }
    }

複製代碼

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

// Realize non-lazy classes (for +load methods and static instances)
    //✅ 實現非懶加載的類,對於load方法和靜態實例變量
    for (EACH_HEADER) {
        //✅ 獲取到非懶加載類的列表
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            //✅  從鏡像列表中映射出來
            Class cls = remapClass(classlist[i]);
            // printf("non-lazy Class:%s\n",cls->mangledName());
            if (!cls) continue;

            // hack for class __ARCLite__, which did not 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
            //✅ 再次插入到表allocatedClasses表中
            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 not disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            //✅ 實現全部非懶加載的類(實例化類對象的一些信息,例如rw)
            realizeClassWithoutSwift(cls);
        }
    }

複製代碼

查看realizeClassWithoutSwift相關代碼

ro表示readonly,是在編譯時刻就已經賦值的,可是此時rw還並無賦值,因此這一步,主要是初始化rw

// fixme verify class is not in an un-dlopened part of the shared cache?

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

經過下面的代碼咱們能夠發現,此時對類的superclassisa進行了處理,會根據類的繼承鏈關係進行遞歸操做,知道類爲nil,也就是NSObject的父類

if (!cls) return nil;
...
supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));

cls->superclass = supercls;
cls->initClassIsa(metacls);
複製代碼

最後,咱們發現函數最後調用了methodizeClass方法,根據明明,猜想是方法等的初始化

static Class realizeClassWithoutSwift(Class cls)
{
    ...
    // Attach categories
    methodizeClass(cls);
    return cls;
}

複製代碼

methodizeClass函數的實現中,咱們發現了,剛剛初始化的rw的賦值,是從ro中取出相關數據,直接賦值給rw

// Install methods and properties that the class implements itself.
    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);
    }
複製代碼

那麼attachLists是如何插入數據的呢

根據代碼咱們能夠發現,主要經過把oldList向後偏移addedCount的位置,而後把新的addedLists總體插入到表的前面,從而實現分類的方法覆蓋本類同名方法,因此分類的方法會比原方法先調用,並無覆蓋

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

一個類的加載的主體流程以下

read_images內部先建立一個全局類的表gdb_objc_realized_classes和一個已經初始化的類的表allocatedClasses,以後對類進行初始化,並加載到表中,而後把SELProtocol等也映射到內存對應的表中中去,和類並非一個表,並在對非懶加載類進行處理的時候,經過realizeClassWithoutSwiftro進行賦值,而且初始化rw,以後經過methodizeClassrw賦值,完成數據的加載

至此,一個類所須要屬性的賦值加載都已經完成

未完待續

相關文章
相關標籤/搜索