Images加載一

Objc中類的初始化是從_objc_init方法開始的。該方法的結(jié)構(gòu)如下圖:

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);
}
首先是調(diào)用的是environ_init();這個方法。

這個方法里面主要是對環(huán)境變量的配置,方法中有段代碼可以打印出所有的環(huán)境變量:

        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);
        }

解鎖這段打印代碼的限制條件,就會打印下面所有的環(huán)境變量:

objc[1473]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[1473]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[1473]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[1473]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[1473]: OBJC_PRINT_RESOLVED_METHODS: log methods created by +resolveClassMethod: and +resolveInstanceMethod:
objc[1473]: OBJC_PRINT_CLASS_SETUP: log progress of class and category setup
objc[1473]: OBJC_PRINT_PROTOCOL_SETUP: log progress of protocol setup
objc[1473]: OBJC_PRINT_IVAR_SETUP: log processing of non-fragile ivars
objc[1473]: OBJC_PRINT_VTABLE_SETUP: log processing of class vtables
objc[1473]: OBJC_PRINT_VTABLE_IMAGES: print vtable images showing overridden methods
objc[1473]: OBJC_PRINT_CACHE_SETUP: log processing of method caches
objc[1473]: OBJC_PRINT_FUTURE_CLASSES: log use of future classes for toll-free bridging
objc[1473]: OBJC_PRINT_PREOPTIMIZATION: log preoptimization courtesy of dyld shared cache
objc[1473]: OBJC_PRINT_CXX_CTORS: log calls to C++ ctors and dtors for instance variables
objc[1473]: OBJC_PRINT_EXCEPTIONS: log exception handling
objc[1473]: OBJC_PRINT_EXCEPTION_THROW: log backtrace of every objc_exception_throw()
objc[1473]: OBJC_PRINT_ALT_HANDLERS: log processing of exception alt handlers
objc[1473]: OBJC_PRINT_REPLACED_METHODS: log methods replaced by category implementations
objc[1473]: OBJC_PRINT_DEPRECATION_WARNINGS: warn about calls to deprecated runtime functions
objc[1473]: OBJC_PRINT_POOL_HIGHWATER: log high-water marks for autorelease pools
objc[1473]: OBJC_PRINT_CUSTOM_RR: log classes with un-optimized custom retain/release methods
objc[1473]: OBJC_PRINT_CUSTOM_AWZ: log classes with un-optimized custom allocWithZone methods
objc[1473]: OBJC_PRINT_RAW_ISA: log classes that require raw pointer isa fields
objc[1473]: OBJC_DEBUG_UNLOAD: warn about poorly-behaving bundles when unloaded
objc[1473]: OBJC_DEBUG_FRAGILE_SUPERCLASSES: warn about subclasses that may have been broken by subsequent changes to superclasses
objc[1473]: OBJC_DEBUG_NIL_SYNC: warn about @synchronized(nil), which does no synchronization
objc[1473]: OBJC_DEBUG_NONFRAGILE_IVARS: capriciously rearrange non-fragile ivars
objc[1473]: OBJC_DEBUG_ALT_HANDLERS: record more info about bad alt handler use
objc[1473]: OBJC_DEBUG_MISSING_POOLS: warn about autorelease with no pool in place, which may be a leak
objc[1473]: OBJC_DEBUG_POOL_ALLOCATION: halt when autorelease pools are popped out of order, and allow heap debuggers to track autorelease pools
objc[1473]: OBJC_DEBUG_DUPLICATE_CLASSES: halt when multiple classes with the same name are present
objc[1473]: OBJC_DEBUG_DONT_CRASH: halt the process by exiting instead of crashing
objc[1473]: OBJC_DISABLE_VTABLES: disable vtable dispatch
objc[1473]: OBJC_DISABLE_PREOPTIMIZATION: disable preoptimization courtesy of dyld shared cache
objc[1473]: OBJC_DISABLE_TAGGED_POINTERS: disable tagged pointer optimization of NSNumber et al.
objc[1473]: OBJC_DISABLE_TAG_OBFUSCATION: disable obfuscation of tagged pointers
objc[1473]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[1473]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork

以上環(huán)境變量中有我們常用的:

  • OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
    該環(huán)境變量會禁止non-pointer isa。這樣所有的對象都會使用正常的,非優(yōu)化的isa。該isa指向?qū)ο笏鶎俚念悺?br>
    non-pointer isa

    上圖是我們正常的環(huán)境,可以看到通過x/4gx打印出對象的地址,地址的第一段為對象的isa,和上面的p/x打印的類對象的地址對比可以發(fā)現(xiàn),兩個地址是不相等了。
    接下來我們設(shè)置環(huán)境變量OBJC_DISABLE_NONPOINTER_ISA:
設(shè)置環(huán)境變量

然后重新運(yùn)行程序:

pointer isa

可以看出對象的isa和類對象的地址是相同的,也就是對象的isa真正指向了所屬的類。然后我們通過po打印isa得到了所屬的類。
現(xiàn)在我們大概明白了環(huán)境變量的意義。

  • OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
    開啟該環(huán)境變量后會打印出所有實(shí)現(xiàn)+(void)load方法的對象。我們可以通過這個來優(yōu)化程序的啟動速度。

接下來我們返回到_objc_init函數(shù),繼續(xù)探究:

tls_init();用于線程的key綁定,我們暫時略過;
然后就是static_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]();
    }
}

通過注釋我們也可以了解到,該函數(shù)的作用是:靜態(tài)函數(shù)初始化。那么為什么不是在dyld中初始化呢?因?yàn)閘ibc調(diào)用_objc_init()是在dyld之前,所以我們這里需要手動調(diào)用初始化。
注意:這里的靜態(tài)函數(shù)是指的系統(tǒng)的靜態(tài)函數(shù),我們自定義的不算在里面。
然后調(diào)用的就是lock_init();,我們跟蹤該函數(shù)發(fā)現(xiàn)函數(shù)實(shí)現(xiàn)為空:

void lock_init(void)
{
}

為什么為空實(shí)現(xiàn)呢?可能是這部分代碼沒有開源。我們繼續(xù)往下走,_objc_init(void)中接下來調(diào)用的是exception_init();

/***********************************************************************
* exception_init
* Initialize libobjc's exception handling system.
* Called by map_images().
**********************************************************************/
void exception_init(void)
{
    old_terminate = std::set_terminate(&_objc_terminate);
}

通過注釋說明,這里是注冊異常的回調(diào)

/***********************************************************************
* _objc_terminate
* Custom std::terminate handler.
*
* The uncaught exception callback is implemented as a std::terminate handler. 
* 1. Check if there's an active exception
* 2. If so, check if it's an Objective-C exception
* 3. If so, call our registered callback with the object.
* 4. Finally, call the previous terminate handler.
**********************************************************************/
static void (*old_terminate)(void) = nil;
static void _objc_terminate(void)
{
    if (PrintExceptions) {
        _objc_inform("EXCEPTIONS: terminating");
    }

    if (! __cxa_current_exception_type()) {
        // No current exception.
        (*old_terminate)();
    }
    else {
        // There is a current exception. Check if it's an objc exception.
        @try {
            __cxa_rethrow();
        } @catch (id e) {
            // It's an objc object. Call Foundation's handler, if any.
            (*uncaught_handler)((id)e);
            (*old_terminate)();
        } @catch (...) {
            // It's not an objc object. Continue to C++ terminate.
            (*old_terminate)();
        }
    }
}

例如我們調(diào)用一個未實(shí)現(xiàn)的方法時,程序就會跑到這里來。
我們繼續(xù)回到_objc_init(void)方法中,接下來調(diào)用的就是_dyld_objc_notify_register(&map_images, load_images, unmap_image);
這個方法就是dyld中的方法

//
// 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);

此方法主要是注冊了三個回調(diào)函數(shù),回調(diào)函數(shù)會在dyld中進(jìn)行調(diào)用。當(dāng)images被maped、unmapped、initiallized時候的回調(diào)。

map_images回調(diào)
map_images中調(diào)用了map_images_nolock(count, paths, mhdrs);然后map_images_nolock中調(diào)用了_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
下面我們來解讀_read_images方法。
在該方法中有個以下的doneOnce判斷

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);
        ......
}

在doneOnce(只執(zhí)行一次)中創(chuàng)建了兩張表:gdb_objc_realized_classes和allocatedClasses。然后我們搜索這兩張表,可以看到它們的定義

// 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

gdb_objc_realized_classes主要是用來存儲那些不在共享緩存中,已經(jīng)初始化或者未初始化的類

/***********************************************************************
* allocatedClasses
* A table of all classes (and metaclasses) which have been allocated
* with objc_allocateClassPair.
**********************************************************************/
static NXHashTable *allocatedClasses = nil;

allocatedClasses主要是用來存儲那些已經(jīng)初始化的類。

我們繼續(xù)往下讀_read_images方法的內(nèi)容,主要包含以下幾點(diǎn):

  • 類處理;
  • 方法編號處理;
  • 協(xié)議處理;
  • 非懶加載類處理;
  • 待處理的類;
  • 分類處理;

上面我們是從宏觀上列出了_read_images的主要的功能,下面我們開始細(xì)致的分析。

類的處理

首先獲取類的list列表:

// 從編譯后的類列表中取出所有類,獲取到的是一個classref_t類型的指針
classref_t *classlist = _getObjc2ClassList(hi, &count);

然后for循環(huán)處理:

for (i = 0; i < count; i++) {
    ......
    // 通過readClass函數(shù)獲取處理后的新類,
    Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
    ......
}

在for循環(huán)中調(diào)用readClass方法,對類進(jìn)行處理。我們進(jìn)入該方法看下,里面調(diào)用下面的兩行代碼

addNamedClass(cls, mangledName, replacing);
addClassTableEntry(cls);

在這兩個方法里面,分別將待處理的class存入到上面我們再doneOnce里面創(chuàng)建的兩張表中。
然后我們返回過來繼續(xù)看_read_images下面的流程。我們暫時略過與方法無關(guān)的內(nèi)容(協(xié)議、方法、分類等)。然后找到方法:

realizeClassWithoutSwift(cls);

進(jìn)入realizeClassWithoutSwift(cls);方法,看到對讀取并且賦值了ro:

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);
    }

然后遞歸的對父類和元類進(jìn)行初始化調(diào)用

    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));

然后綁定賦值綁定superClass以及isa

    // Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);

初始化創(chuàng)建superClass后,同樣也會將superClass的subClass執(zhí)行本class。

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

我們繼續(xù)往下看,發(fā)現(xiàn)調(diào)用了下面的方法:

    // Attach categories
    methodizeClass(cls);

進(jìn)入該方法看看

auto rw = cls->data();
auto ro = rw->ro;

讀取到rw和ro

    // 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);
    }

將ro中的methoList attach到rw中

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

將ro中的propertyList attach到rw中

    protocol_list_t *protolist = ro->baseProtocols;
    if (protolist) {
        rw->protocols.attachLists(&protolist, 1);
    }

將ro中的protocolList attach到rw中。
上面代碼就是講ro中的list 貼到rw中。為什么要這樣做呢?我們搜索attachLists方法,發(fā)現(xiàn)在
addMethod
class_addProtocol
_class_addProperty
attachCategories
中都調(diào)用了attachLists方法,所以可以推測,我們平時用的分類方法、協(xié)議方法、動態(tài)的增加方法等都會添加到rw中。而ro中只是類中實(shí)現(xiàn)的方法。
tips: rw是readwrite的縮寫,是可以動態(tài)修改的。ro是readonly的縮寫,在編譯時期確定的,不能動態(tài)修改。比如用戶的ivarsList就存儲在ro,所以我們不能動態(tài)的給對象添加成員變量。
下面我們來看下attachLists方法是怎樣對方法或者協(xié)議等列表進(jìn)行attach處理的?

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]));
        }
    }

如果現(xiàn)在list中已經(jīng)存在了一個Array。那么就會開辟一個新的list空間,將原來的list move到新創(chuàng)建的list的末尾,然后新插入的list copy到新創(chuàng)建list的頭部。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容