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

1. 回顧

在前兩篇博文中半沽,已經(jīng)對dyld動態(tài)鏈接器的底層源碼進行了探索分析,但是dyld鏈接images鏡像文件到內存的過程我們還不知道喘垂,接下來的幾篇博文就著重去探索艰躺。

iOS底層探索之dyld(上):動態(tài)鏈接器流程分析

iOS底層探索之dyld(下):動態(tài)鏈接器流程源碼分析

在這里插入圖片描述

_objc_init方法向dyld中注冊了回調函數(shù),下面就補充一點內容顿天,探究下_objc_init方法堂氯。

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() : 讀取影響運?時的環(huán)境變量。如果需要牌废,還可以打印環(huán)境變量幫助咽白。
  • tls_init():關于線程key的綁定 - ?如每線程數(shù)據(jù)的析構函數(shù)
  • static_init() :運?C ++靜態(tài)構造函數(shù)。在dyld調?我們的靜態(tài)構造函數(shù)之前鸟缕,libc 會調?_objc_init()晶框,因此我們必須??做
  • lock_init(): 沒有重寫,采?C++的特性
  • exception_init () 初始化libobjc的異常處理系統(tǒng)
  • cache_init(): 緩存條件初始化
  • runtime_init() : runtime運?時環(huán)境初始化,??主要
    是:unattachedCategories,allocatedClasses 后?會分析
  • _imp_implementationWithBlock_init :啟動回調機制。通常這不會做什么懂从,因為所有的初始化都
    是惰性的授段,但是對于某些進程,我們會迫不及待地加載trampolines dylib番甩。

2.1 environ_init

environ_init()主要代碼如下侵贵,在PrintHelp或者PrintOptions條件下,可以在控制臺打印輸出所有的環(huán)境變量缘薛。

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

那么現(xiàn)在把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);
  }

打印如果結果如下:

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

這么多環(huán)境變量敦间,看著就暈瓶逃,有沒有我們熟悉的笆?厢绝?契沫?

有比如:

  • OBJC_PRINT_IMAGES打印鏡像文件
  • OBJC_DISABLE_NONPOINTER_ISA判斷是否是優(yōu)化的指針
  • OBJC_PRINT_LOAD_METHODS打印出程序中所有的load方法等等。

那么我們沒有objc的源碼該如何查看環(huán)境變量呢昔汉?
可以使用終端命令export OBJC_HELP=1打印如下

2.1.1 終端命令查看環(huán)境變量

終端命令查看環(huán)境變量

2.1.2 Xcode工程設置環(huán)境變量

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

Xcode設置環(huán)境變量

2.1.2.1 OBJC_DISABLE_NONPOINTER_ISA

設置環(huán)境變量OBJC_DISABLE_NONPOINTER_ISA表示是否開啟ISA指針優(yōu)化懈万。YES表示純指針,NO表示優(yōu)化后的指針就是nonpointer isa靶病。
iOS底層探索之對象的本質和類的關聯(lián)特性initIsa(下)博客中介紹過了ISA会通。

首先不設置環(huán)境變量看下nonpointer isa

查看isa

isa低位0號位是1,表示是優(yōu)化后的isa娄周,而且高位上也有其他的數(shù)據(jù)

將環(huán)境變量OBJC_DISABLE_NONPOINTER_ISA = YES設置為YES 再看看isa是怎么樣的涕侈。

在這里插入圖片描述

isa低位0號位是0,表示是isa是純指針昆咽,而且高位除了cls也沒有其他的數(shù)據(jù)了。

2.1.2.2 OBJC_PRINT_LOAD_METHODS

環(huán)境變量OBJC_PRINT_LOAD_METHODS打印出程序中所有的load方法牙甫,在自定義類中添加load方法掷酗,配置環(huán)境變量OBJC_PRINT_LOAD_METHODS = YES

OBJC_PRINT_LOAD_METHODS

+[JPStudent load]這個是我們自定義JPStudent類中的load方法,其它的都是系統(tǒng)級別的load方法窟哺。load方法太多會導致你應用程序啟動變慢泻轰,或者有的人在load方法里面做一些隱藏的操作,那么就可以通過這個環(huán)境變量檢查出哪個類里面有實現(xiàn)的load方法且轨。

2.2 tls_init

tls_init關于線程key的綁定浮声,比如每個線程數(shù)據(jù)的析構函數(shù)。

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

2.3 static_init

全局靜態(tài)C++函數(shù)旋奢,在dyld調用我們的靜態(tài)構造函數(shù)之前泳挥,libobjc會調用_objc_init,會先調用自己的C++構造函數(shù)至朗,簡單說的就是libobjc會調用自己的全局的C++函數(shù)屉符,因為這個全局的構函數(shù)是個非常重要的函數(shù),為了及時性锹引,就自己先調用起來矗钟,從底層源碼的注釋也可以知道,先調用自己的全局靜態(tài)C++函數(shù)再走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運行時環(huán)境初始化吨艇,里面主要是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);
 }

當你的代碼出現(xiàn)crashcrash并不是代碼錯誤软啼,不一定會奔潰桑谍。crash是在上層的編寫的代碼出現(xiàn)一些不符合蘋果系統(tǒng)底層的邏輯和規(guī)則時系統(tǒng)會發(fā)出異常的信號。通過出現(xiàn)異常會進去_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方法里面發(fā)現(xiàn)了(*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告訴程序員可以自己傳一個函數(shù)的句柄,fn可以是程序員自己定義的函數(shù)贿条,然后回調時可以自己處理程序拋出的異常的信息雹仿。

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注冊回調公黑,這個方法非常重要R厣獭!凡蚜!

_dyld_objc_notify_register

  • _dyld_objc_notify_register源碼實現(xiàn)如下:
// _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(下):動態(tài)鏈接器流程源碼分析 博客中也已經(jīng)介紹了人断,那么我們再來回顧下。

  • &map_imagesdyldimage鏡像文件加載到內存中會調用該函數(shù)朝蜘。
  • load_imagesdyld初始化所有的image鏡像文件文件會調用恶迈。
  • unmap_image:將image鏡像文件移除時會調用。

load_images方法其實就是調用load方法谱醇,map_image方法暇仲,&map_images是指針傳遞,指向是同一塊實現(xiàn)的地址副渴,如果有什么變化就可以第一時間知道奈附。在dyldsNotifyObjCMapped調用的地方是在notifyBatchPartial方法中,而notifyBatchPartial方法是在registerObjCNotifiers中調用煮剧,在objc初始化注冊通知時就調用了桅狠,所以是先調用map_images后調用load_images這么個函數(shù)執(zhí)行順序。

那么接下就去看看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方法漩符,發(fā)現(xiàn)代碼太多了,無從下手驱还,只能從全局來分析了嗜暴,于是又把代碼折疊起來凸克。

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
    // 當類中有協(xié)議時:`readProtocol`
    // Discover protocols. Fix up protocol refs.
    for (EACH_HEADER) { ... }
    ts.log("IMAGE TIMES: discover protocols");
    
    // 修復沒有被加載的協(xié)議
    // 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");
    
    // 沒有被處理的類,優(yōu)化那些被侵犯的類
    // Realize newly-resolved future classes, in case CF manipulates them
    if (resolvedFutureClasses) { ... }
    ts.log("IMAGE TIMES: realize future classes");
   ...
#undef EACH_HEADER

}

從大體上可以看出是對一些log日志的打印輸出闷沥,主要如下:

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

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

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;  
        //創(chuàng)建哈希表 存放所有的類
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        ts.log("IMAGE TIMES: first time tasks");
  }

加載一次下次就不會再次進入判斷蚂维。第一次進來主要創(chuàng)建表gdb_objc_realized_classes,表里存放所有的類不管是實現(xiàn)的還是未實現(xiàn)的都存放在里面路狮,是一張總表虫啥。

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);
        
        // 類信息發(fā)生混亂,類運行時可能發(fā)生移動辱匿,但是沒有被刪除,相當于常說的野指針
        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此時還沒有賦值丝里,但是系統(tǒng)會隨機給newCls分配一塊臟地址,斷點向下走到if判斷處体谒,再次控制臺lldb調試看看賦值過后是什么值:

readClass賦值后

從以上驗證可以看出readClass方法的作用:就是把類名和地址關聯(lián)起來杯聚,那么現(xiàn)在去源碼看看

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
            //將類名和地址關聯(lián)起來
            addNamedClass(cls, mangledName, replacing);
        } else { ...}
        //將關聯(lián)的類插入到另一張哈希表中
        addClassTableEntry(cls);
    }
    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) { ... }
    return cls;

}
  
  • nonlazyMangledName獲取類名。
  • addNamedClass將類名和地址關聯(lián)綁定起來抒痒。
  • addClassTableEntry將關聯(lián)的類插入到哈希表中幌绍,這張表中都是初始化過的類。

為了便于研究故响,我們可以對cls進行過濾傀广,過濾出我們要研究的類JPStudent

readClass中過濾類

在此流程中會通過cls->nonlazyMangledName()獲取類的名稱彩届,nonlazyMangledName源碼實現(xiàn)如下:

  • 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 將類名和地址關聯(lián)綁定起來
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 實現(xiàn)部分代碼
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運行時環(huán)境初始化樟蠕,主要是unattachedCategoriesallocatedClasses兩張表贮聂,此時addClassTableEntry的操作是插入allocatedClasses表中靠柑。與此同時還對元類進行了相應的處理,在處理類的時候吓懈,元類也得處理歼冰。

通過源碼分析、斷點調試耻警,發(fā)現(xiàn)對rwro的獲取和賦值操作隔嫡,并不在readClass里面,于是又回到_read_images去看看榕栏。

_read_images斷點調試

_read_images方法進行畔勤,斷點跟蹤,一步一步走扒磁,發(fā)現(xiàn)最后走到了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這整個流程就串聯(lián)起來了,接下來的幾篇博客將對類的加載底層原理進行探索分析兰伤!

請持續(xù)關注内颗!敬請期待!

4.總結

  • 環(huán)境變量可以通過Xcode設置和終端命令export OBJC_HELP=1打印查看
  • _read_images是對一些log信息的打印
  • readClass把類名和地址關聯(lián)起來
  • rw的賦值和ro的獲取并不在readClass里面
  • _dyld_objc_notify_register 執(zhí)行流程圖
    _dyld_objc_notify_register執(zhí)行流程圖

    更多內容持續(xù)更新

?? 喜歡就點個贊吧????

?? 覺得學習到了的敦腔,可以來一波均澳,收藏+關注,評論 + 轉發(fā)符衔,以免你下次找不到我????

??歡迎大家留言交流找前,批評指正,互相學習??判族,提升自我??

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末躺盛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子形帮,更是在濱河造成了極大的恐慌槽惫,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辩撑,死亡現(xiàn)場離奇詭異界斜,居然都是意外死亡,警方通過查閱死者的電腦和手機合冀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門各薇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人水慨,你說我怎么就攤上這事得糜【纯福” “怎么了?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵朝抖,是天一觀的道長啥箭。 經(jīng)常有香客問我,道長治宣,這世上最難降的妖魔是什么急侥? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮侮邀,結果婚禮上坏怪,老公的妹妹穿的比我還像新娘。我一直安慰自己绊茧,他們只是感情好铝宵,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著华畏,像睡著了一般鹏秋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上亡笑,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天侣夷,我揣著相機與錄音,去河邊找鬼仑乌。 笑死百拓,一個胖子當著我的面吹牛,可吹牛的內容都是我干的晰甚。 我是一名探鬼主播衙传,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼压汪!你這毒婦竟也來了粪牲?” 一聲冷哼從身側響起古瓤,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤止剖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后落君,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體穿香,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年绎速,在試婚紗的時候發(fā)現(xiàn)自己被綠了皮获。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡纹冤,死狀恐怖洒宝,靈堂內的尸體忽然破棺而出购公,到底是詐尸還是另有隱情,我是刑警寧澤雁歌,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布宏浩,位于F島的核電站,受9級特大地震影響靠瞎,放射性物質發(fā)生泄漏比庄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一乏盐、第九天 我趴在偏房一處隱蔽的房頂上張望佳窑。 院中可真熱鬧,春花似錦父能、人聲如沸神凑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耙厚。三九已至,卻和暖如春岔霸,著一層夾襖步出監(jiān)牢的瞬間薛躬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工呆细, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留型宝,地道東北人。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓絮爷,卻偏偏與公主長得像趴酣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子坑夯,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內容