iOS底層探索之類的加載原理(一):read_images分析

1. 回顧

在前兩篇博文中,已經對dyld動態連接器的底層源碼進行了探索分析,可是dyld連接images鏡像文件到內存的過程咱們還不知道,接下來的幾篇博文就着重去探索。程序員

iOS底層探索之dyld(上):動態連接器流程分析緩存

iOS底層探索之dyld(下):動態連接器流程源碼分析markdown

_objc_init方法向dyld中註冊了回調函數,下面就補充一點內容,探究下_objc_init方法。app

2. _objc_init簡單分析

先來看看_objc_init的底層源碼函數

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();
    runtime_init();
    exception_init();

#if __OBJC2__
    cache_t::init();
#endif
    _imp_implementationWithBlock_init();
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
 
#if __OBJC2__
    didCallDyldNotifyRegister = true ;
#endif
}

複製代碼
  • environ_init() : 讀取影響運⾏時的環境變量。若是須要,還能夠打印環境變量幫助。
  • tls_init() :關於線程key的綁定 - ⽐如每線程數據的析構函數
  • static_init() :運⾏C ++靜態構造函數。在dyld調⽤咱們的靜態構造函數以前,libc 會調⽤ _objc_init(),所以咱們必須⾃⼰作
  • lock_init(): 沒有重寫,採⽤C++ 的特性
  • exception_init () 初始化libobjc的異常處理系統
  • cache_init(): 緩存條件初始化
  • runtime_init() : runtime運⾏時環境初始化,⾥⾯主要

是:unattachedCategories,allocatedClasses 後⾯會分析oop

  • _imp_implementationWithBlock_init :啓動回調機制。一般這不會作什麼,由於全部的初始化都

是惰性的,可是對於某些進程,咱們會火燒眉毛地加載trampolines dylib源碼分析

2.1 environ_init

environ_init()主要代碼以下,在PrintHelp或者PrintOptions條件下,能夠在控制檯打印輸出全部的環境變量。學習

void  environ_init(void) 

{    
     // Print OBJC_HELP and OBJC_PRINT_OPTIONS output.
    if(PrintHelp || PrintOptions) { 
         ...
    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);
        }
    }
}

複製代碼

那麼如今把if(PrintHelp)if(PrintOptions && *opt->var)判斷條件都去掉,看看控制檯打印了哪些東西。優化

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

打印結果以下:ui

objc[2964]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[2964]: OBJC_PRINT_IMAGES is set
objc[2964]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[2964]: OBJC_PRINT_IMAGE_TIMES is set
objc[2964]: OBJC_PRINT_LOAD_METHODS: log calls to class and category +load methods
objc[2964]: OBJC_PRINT_LOAD_METHODS is set
objc[2964]: OBJC_PRINT_INITIALIZE_METHODS: log calls to class +initialize methods
objc[2964]: OBJC_PRINT_INITIALIZE_METHODS is set
。。。。。。省略。。。。。。
objc[2964]: OBJC_DISABLE_NONPOINTER_ISA: disable non-pointer isa fields
objc[2964]: OBJC_DISABLE_NONPOINTER_ISA is set
objc[2964]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY: disable safety checks for +initialize after fork
objc[2964]: OBJC_DISABLE_INITIALIZE_FORK_SAFETY is set
objc[2964]: OBJC_DISABLE_FAULTS: disable os faults
objc[2964]: OBJC_DISABLE_FAULTS is set
objc[2964]: OBJC_DISABLE_PREOPTIMIZED_CACHES: disable preoptimized caches
objc[2964]: OBJC_DISABLE_PREOPTIMIZED_CACHES is set
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING: disable coalescing of autorelease pool pointers
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING is set
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU: disable coalescing of autorelease pool pointers using look back N strategy
objc[2964]: OBJC_DISABLE_AUTORELEASE_COALESCING_LRU is set
複製代碼

這麼多環境變量,看着就暈,有沒有咱們熟悉的啊???

有好比:

  • OBJC_PRINT_IMAGES打印鏡像文件
  • OBJC_DISABLE_NONPOINTER_ISA判斷是不是優化的指針
  • OBJC_PRINT_LOAD_METHODS打印出程序中全部的load方法等等。

那麼我要是沒有objc的源碼該如何查看環境變量呢? 可使用終端命令export OBJC_HELP=1打印以下

2.1.1 終端命令查看環境變量

終端命令查看環境變量

2.1.2 Xcode工程設置環境變量

Xcode中環境變量配置的位置:選中運行的target--> Edit scheme... --> Run --> Arguments --> Environment Variables

Xcode設置環境變量

2.1.2.1 OBJC_DISABLE_NONPOINTER_ISA

設置環境變量OBJC_DISABLE_NONPOINTER_ISA表示是否開啓ISA指針優化。YES表示純指針,NO表示優化後的指針就是nonpointer isa。 在iOS底層探索之對象的本質和類的關聯特性initIsa(下)博客中介紹過了ISA

首先不設置環境變量看下nonpointer isa

查看isa

isa低位0號位是1,表示是優化後的isa,並且高位上也有其餘的數據

將環境變量OBJC_DISABLE_NONPOINTER_ISA = YES設置爲YES 再看看isa是怎麼樣的。

在這裏插入圖片描述

isa低位0號位是0,表示是isa是純指針,並且高位除了cls也沒有其餘的數據了。

2.1.2.2 OBJC_PRINT_LOAD_METHODS

環境變量OBJC_PRINT_LOAD_METHODS打印出程序中全部的load方法,在自定義類中添加load方法,配置環境變量OBJC_PRINT_LOAD_METHODS = YES

OBJC_PRINT_LOAD_METHODS

+[JPStudent load] 這個是咱們自定義JPStudent類中的load方法,其它的都是系統級別的load方法。load方法太多會致使你應用程序啓動變慢,或者有的人在load方法裏面作一些隱藏的操做,那麼就能夠經過這個環境變量檢查出哪一個類裏面有實現的load方法。

2.2 tls_init

tls_init關於線程key的綁定,好比每一個線程數據的析構函數。

void  tls_init(void)
{ 
#if SUPPORT_DIRECT_THREAD_KEYS
    //建立線程的詹存緩存池
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
    //析構函數
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}

複製代碼

2.3 static_init

全局靜態C++函數,在dyld調用咱們的靜態構造函數以前,libobjc會調用_objc_init,會先調用本身的C++構造函數,簡單說的就是libobjc會調用本身的全局的C++函數,由於這個全局的構函數是個很是重要的函數,爲了及時性,就本身先調用起來,從底層源碼的註釋也能夠知道,先調用本身的全局靜態C++函數再走dyld

/*********************************************************************** * 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]();
    }
    auto offsets = getLibobjcInitializerOffsets(&_mh_dylib_header, &count);
    for(size_t i = 0; i < count; i++) {
        UnsignedInitializer init(offsets[i]);
        init();
    }
}

複製代碼

2.4 runtime_init

runtime運行時環境初始化,裏面主要是unattachedCategoriesallocatedClasses兩張表的初始化。

void runtime_init(void)
{  
   objc::unattachedCategories.init(32);//分類表的初始化
   objc::allocatedClasses.init();//類表的初始化
}

複製代碼

2.5 exception_init

初始化libobjc庫的異常處理,這個和objcdyld中註冊回調差很少的意思,就是讓你去處理異常的。

void  exception_init(void)
 {
  old_terminate = std::set_terminate(&_objc_terminate);
 }

複製代碼

當你的代碼出現crashcrash並非代碼錯誤,不必定會奔潰。crash是在上層的編寫的代碼出現一些不符合蘋果系統底層的邏輯和規則時系統會發出異常的信號。經過出現異常會進去_objc_terminate方法。

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

複製代碼

_objc_terminate方法裏面發現了(*uncaught_handler)((id)e)它會把異常拋出去,全局搜索uncaught_handler

objc_uncaught_exception_handler 
objc_setUncaughtExceptionHandler(objc_uncaught_exception_handler fn)
{
    objc_uncaught_exception_handler result = uncaught_handler;
    uncaught_handler = fn;
    return result;
}

複製代碼

uncaught_handler = fn 告訴程序員能夠本身傳一個函數的句柄,fn能夠是程序員本身定義的函數,而後回調時能夠本身處理程序拋出的異常的信息。

2.6 cache_t::init

緩存條件的初始化

void cache_t::init()
{
#if HAVE_TASK_RESTARTABLE_RANGES
    mach_msg_type_number_t count = 0;
    kern_return_t kr;
    while (objc_restartableRanges[count].location) {
        count++;
    }
    //開啓緩存
    kr = task_restartable_ranges_register(mach_task_self(),
                                         objc_restartableRanges, count)
    if (kr == KERN_SUCCESS) return;
    _objc_fatal("task_restartable_ranges_register failed (result 0x%x: %s)",
                kr, mach_error_string(kr));
#endif // HAVE_TASK_RESTARTABLE_RANGES
}

複製代碼

2.7 _imp_implementationWithBlock_init

這個是啓動回調機制,一般不會作什麼。由於全部的初始化都是惰性的,可是對於某些進程會火燒眉毛的加載 trampolines dylib_imp_implementationWithBlock_init源碼以下:

void
_imp_implementationWithBlock_init(void)
{
#if TARGET_OS_OSX
    // Eagerly load libobjc-trampolines.dylib in certain processes. Some
    // programs (most notably QtWebEngineProcess used by older versions of
    // embedded Chromium) enable a highly restrictive sandbox profile which
    // blocks access to that dylib. If anything calls
    // imp_implementationWithBlock (as AppKit has started doing) then we'll
    // crash trying to load it. Loading it here sets it up before the sandbox
    // profile is enabled and blocks it.
    //
    // This fixes EA Origin (rdar://problem/50813789)
    // and Steam (rdar://problem/55286131)
    if (__progname &&
        (strcmp(__progname, "QtWebEngineProcess") == 0 ||
         strcmp(__progname, "Steam Helper") == 0)) {
        Trampolines.Initialize();
    }
#endif
}

複製代碼

2.8 _dyld_objc_notify_register

_dyld_objc_notify_registerdyld註冊回調,這個方法很是重要!!!

_dyld_objc_notify_register

  • _dyld_objc_notify_register源碼實現以下:
// _dyld_objc_notify_init
void registerObjCNotifiers(_dyld_objc_notify_mapped mapped, _dyld_objc_notify_init 
init, _dyld_objc_notify_unmapped unmapped)
{
// record functions to call
sNotifyObjCMapped = mapped;
sNotifyObjCInit = init;
sNotifyObjCUnmapped = unmapped;
// call 'mapped' function with all images mapped so far
try {
    notifyBatchPartial(dyld_image_state_bound, true, NULL, false, true);
}
catch (const char* msg) {
// ignore request to abort during registration
}
// call 'init' function on all images already init'ed (below libSystem)
for (std::vector<ImageLoader*>::iterator it=sAllImages.begin(); it !=
sAllImages.end(); it++) {
ImageLoader* image = *it;
if ( (image->getState() == dyld_image_state_initialized) && image->notifyObjC() )  {
dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
     (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
    }
  }
}

複製代碼

其實在iOS底層探索之dyld(下):動態連接器流程源碼分析 博客中也已經介紹了,那麼咱們再來回顧下。

  • &map_imagesdyldimage鏡像文件加載到內存中會調用該函數。
  • load_imagesdyld初始化全部的image鏡像文件文件會調用。
  • unmap_image:將image鏡像文件移除時會調用。

load_images方法其實就是調用load方法,map_image方法,&map_images是指針傳遞,指向是同一塊實現的地址,若是有什麼變化就能夠第一時間知道。在dyldsNotifyObjCMapped調用的地方是在notifyBatchPartial方法中,而notifyBatchPartial方法是在registerObjCNotifiers中調用,在objc初始化註冊通知時就調用了,因此是先調用map_images後調用load_images這麼個函數執行順序。

那麼接下就去看看map_images

3. map_images

3.1 map_images

進入map_images源碼

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

}

複製代碼

3.2 map_images_nolock

  • 進入OBJC_PRINT_LOAD_METHODS源碼

map_images_nolock

咱們要找的無非就是鏡像文件的加載和映射相關的,其餘的代碼就不用看了,直接摺疊起來,去重點代碼裏面看看。

3.3 read_images

查看_read_images方法,發現代碼太多了,無從下手,只能從全局來分析了,因而又把代碼摺疊起來。

void _read_images(header_info **hList, uint32_t hCount, int 
totalClasses, int 
unoptimizedTotalClasses)
{
   ... //表示省略部分代碼
#define EACH_HEADER \ hIndex = 0; \ hIndex < hCount && (hi = hList[hIndex]); \ hIndex++
    // 條件控制進行一次的加載
    if (!doneOnce) { ... }
    // 修復預編譯階段的`@selector`的混亂的問題
    // 就是不一樣類中有相同的方法 可是相同的方法地址是不同的
    // Fix up @selector references
    static size_t UnfixedSelectors;
    { ... }
    ts.log("IMAGE TIMES: fix up selector references");
    
    // 錯誤混亂的類處理
    // Discover classes. Fix up unresolved future classes. Mark bundle classes.
    bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover classes");
    
    // 修復重映射一些沒有被鏡像文件加載進來的類
    // Fix up remapped classes
    // Class list and nonlazy class list remain unremapped.
    // Class refs and super refs are remapped for message dispatching.
    if (!noClassesRemapped()) { ... }
    ts.log("IMAGE TIMES: remap classes");

#if SUPPORT_FIXUP
    // 修復一些消息
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");

#endif
    // 當類中有協議時:`readProtocol`
    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover protocols");
    
    // 修復沒有被加載的協議
    // Fix up @protocol references
    // Preoptimized images may have the right 
    // answer already but we don't know for sure.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: fix up @protocol references");
    
    // 分類的處理
    // Discover categories. Only do this after the initial category
    // attachment has been done. For categories present at startup,
    // discovery is deferred until the first load_images call after
    // the call to _dyld_objc_notify_register completes. 
    if (didInitialAttachCategories) { ... }
    ts.log("IMAGE TIMES: discover categories");
    
    // 類的加載處理
    // Category discovery MUST BE Late to avoid potential races
    // when other threads call the new category code befor
    // this thread finishes its fixups.
    // +load handled by prepare_load_methods()
    // Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: realize non-lazy classes");
    
    // 沒有被處理的類,優化那些被侵犯的類
    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) { ... }
    ts.log("IMAGE TIMES: realize future classes");
   ...
#undef EACH_HEADER

}

複製代碼

從大致上能夠看出是對一些log日誌的打印輸出,主要以下:

  • 條件控制進行一次加載
  • 修復預編譯階段的@selector的混亂的問題
  • 錯誤混亂的類處理
  • 修復重映射一些沒有被鏡像文件加載進來的類
  • 修復一些消息
  • 當類中有協議時:readProtocol
  • 修復沒有被加載的協議
  • 分類的處理
  • 類的加載處理
  • 沒有被處理的類,優化那些被侵犯的類

下面就重點分析幾個比較主要的;

3.3.1 doneOnce

  • doneOnce
if(!doneOnce) {
        doneOnce = YES; // 加載一次後,就不會在進判斷 doneOnce = YES
        launchTime = YES;
         ...
        // 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);
        ts.log("IMAGE TIMES: first time tasks");
  }

複製代碼

加載一次下次就不會再次進入判斷。第一次進來主要建立表gdb_objc_realized_classes,表裏存放全部的類不論是實現的仍是未實現的都存放在裏面,是一張總表。

3.3.1 UnfixedSelectors

  • UnfixedSelectors
static size_t UnfixedSelectors;
{
    mutex_locker_t lock(selLock);
    for (EACH_HEADER) {
        if (hi->hasPreoptimizedSelectors()) continue;
        bool isBundle = hi->isBundle();
        // 從macho文件中獲取方法名列表
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) {
            const char *name = sel_cname(sels[i]);
            SEL sel = sel_registerNameNoLock(name, isBundle);//從dyld裏面獲取方法名稱
            if (sels[i] != sel) {
                sels[i] = sel;
            }
        }
    }
}

複製代碼

不一樣類中可能存在相同的方法,可是相同的方法地址是不一樣的.

相同方法地址不一樣

sels[i]_getObjc2SelectorRefs是從MachO裏面獲取的,MachO有相對位移地址和偏移地址,selsel_registerNameNoLockdyld裏面獲取,dyld是連接整個程序的,因此以dyld的爲準。由於方法是存放在類中,每一個類中的位置是不同的,因此方法的地址也就不同,那麼就必須對那些混亂的方法進行修復處理。

for (EACH_HEADER) {
    if (! mustReadClasses(hi, hasDyldRoots)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }
    //從macho中讀取類列表信息
    classref_t const *classlist = _getObjc2ClassList(hi, &count);
    bool headerIsBundle = hi->isBundle();
    bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

    for (i = 0; i < count; i++) {
        Class cls = (Class)classlist[i];
        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賦值前

cls指向的是一塊內存地址,newCls此時尚未賦值,可是系統會隨機給newCls分配一塊髒地址,斷點向下走到if判斷處,再次控制檯lldb調試看看賦值事後是什麼值:

readClass賦值後 從以上驗證能夠看出readClass方法的做用:就是把類名和地址關聯起來,那麼如今去源碼看看

3.3.3 readClass

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    // 獲取類名
    const char *mangledName = cls->nonlazyMangledName();
    if (missingWeakSuperclass(cls)) { ... }
    cls->fixupBackwardDeployingStableSwift();
    Class replacing = nil;

    if (mangledName != nullptr) { ... }

    if (headerIsPreoptimized  &&  !replacing) {...
    } else {
        if (mangledName) { 
        //some Swift generic classes can lazily generate their names
            //將類名和地址關聯起來
            addNamedClass(cls, mangledName, replacing);
        } else { ...}
        //將關聯的類插入到另外一張哈希表中
        addClassTableEntry(cls);
    }
    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) { ... }
    return cls;

}
  
複製代碼
  • nonlazyMangledName獲取類名。
  • addNamedClass將類名和地址關聯綁定起來。
  • addClassTableEntry將關聯的類插入到哈希表中,這張表中都是初始化過的類。

爲了便於研究,咱們能夠對cls進行過濾,過濾出咱們要研究的類JPStudent

readClass中過濾類

在此流程中會經過cls->nonlazyMangledName()獲取類的名稱,nonlazyMangledName源碼實現以下:

  • nonlazyMangledName
// Get the class's mangled name, or NULL if the class has a lazy
    // name that hasn't been created yet.
    const char *nonlazyMangledName() const {
        return bits.safe_ro()->getName();
    }
複製代碼
  • safe_ro
// Get the class's ro data, even in the presence of concurrent realization.
    // fixme this isn't really safe without a compiler barrier at least
    // and probably a memory barrier when realizeClass changes the data field
    const class_ro_t *safe_ro() const {
        class_rw_t *maybe_rw = data();
        if (maybe_rw->flags & RW_REALIZED) {
            // maybe_rw is rw
            return maybe_rw->ro();
        } else {
            // maybe_rw is actually ro
            return (class_ro_t *)maybe_rw;
        }
    }
複製代碼
  • addNamedClass 將類名和地址關聯綁定起來
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());
}
複製代碼

經過NXMapInsert方法更新gdb_objc_realized_classes哈希表,keynamevaluecls

  • NXMapInsert 實現部分代碼
void *NXMapInsert(NXMapTable *table, const void *key, const void *value) {
    MapPair	*pairs = (MapPair *)table->buckets;
    unsigned	index = bucketOf(table, key);
    MapPair	*pair = pairs + index;
    if (key == NX_MAPNOTAKEY) {
	_objc_inform("*** NXMapInsert: invalid key: -1\n");
	return NULL;
    }

    unsigned numBuckets = table->nbBucketsMinusOne + 1;

    if (pair->key == NX_MAPNOTAKEY) {
	pair->key = key; pair->value = value;
	table->count++;
	if (table->count * 4 > numBuckets * 3) _NXMapRehash(table);
	return NULL;
    }
..... 省略代碼  ......
}    
複製代碼
  • addClassTableEntry
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.
    // allocatedClasses
    auto &set = objc::allocatedClasses.get();
    ASSERT(set.find(cls) == set.end());
    if (!isKnownClass(cls))
        set.insert(cls);
    if (addMeta)
        //將元類插入哈希表中
        addClassTableEntry(cls->ISA(), false);
}

複製代碼

allocatedClasses_objc_initruntime_init運行時環境初始化,主要是unattachedCategoriesallocatedClasses兩張表,此時addClassTableEntry的操做是插入allocatedClasses表中。與此同時還對元類進行了相應的處理,在處理類的時候,元類也得處理。

經過源碼分析、斷點調試,發現對rwro的獲取和賦值操做,並不在readClass裏面,因而又回到_read_images去看看。

_read_images斷點調試

_read_images方法進行,斷點跟蹤,一步一步走,發現最後走到了realizeClassWithoutSwift處,那麼進去看看。

  • realizeClassWithoutSwift 源碼以下:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    class_rw_t *rw;
    Class supercls;
    Class metacls;

    if (!cls) return nil;
    if (cls->isRealized()) {
        validateAlreadyRealizedClass(cls);
        return cls;
    }
    ASSERT(cls == remapClass(cls));
    
..... 省略代碼...........
}
複製代碼

好傢伙,原來對rwro的獲取和賦值操做是在realizeClassWithoutSwift裏面進行了處理,從dyld_objc_init再到read_images 這整個流程就串聯起來了,接下來的幾篇博客也主要是對類的加載底層原理進行探索分析!

請持續關注!敬請期待!

4.總結

  • 環境變量能夠經過Xcode設置和終端命令export OBJC_HELP=1打印查看
  • _read_images是對一些log信息的打印
  • readClass把類名和地址關聯起來
  • rw的賦值和ro的獲取並不在readClass裏面
  • _dyld_objc_notify_register 執行流程圖

_dyld_objc_notify_register執行流程圖

更多內容持續更新

🌹 喜歡就點個贊吧👍🌹

🌹 以爲學習到了的,能夠來一波,收藏+關注,評論 + 轉發,以避免你下次找不到我😁🌹

🌹歡迎你們留言交流,批評指正,互相學習😁,提高自我🌹

相關文章
相關標籤/搜索