OC底層原理16-objc_init初探 & dyld和objc關(guān)聯(lián)

  • 引入
    OC底層原理15-dyld加載流程 中奈懒,我們初步窺探了dyld的加載流程:_dyld_start -> dyldbootstrap -> dyld::_main奕短,在dyld::_main流程的最后一步尋找主程序入口益眉,我們進(jìn)入了recursiveInitialization方法,首先調(diào)用了context.notifySingle方法進(jìn)行單個通知注入承疲,調(diào)用回調(diào)函數(shù)load_images残揉,調(diào)用doInitialization方法的時候,會從dyld -> libSystem -> libdispatch -> objc中的_objc_init疲扎,_objc_init又調(diào)用了dyld中實現(xiàn)的_dyld_objc_notify_register注冊回調(diào)函數(shù),這樣就構(gòu)成了跨庫閉環(huán)捷雕,如下圖
    recursiveInitialization流程.png

一、_objc_init源碼初探

image.png
1.1壹甥、environ_init()

讀取影響運行時的環(huán)境變量

  • 進(jìn)入 objc可編譯源碼
  • 修改void environ_init(void)方法如下救巷,打印所以設(shè)置的環(huán)境變量
  • 運行打印環(huán)境變量
void environ_init(void) 
{
    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[11514]: OBJC_PRINT_IMAGES: log image and library names as they are loaded
objc[11514]: OBJC_PRINT_IMAGES is set
objc[11514]: OBJC_PRINT_IMAGE_TIMES: measure duration of image loading steps
objc[11514]: OBJC_PRINT_IMAGE_TIMES is set
...
  • 環(huán)境變量的設(shè)置 Product -> Scheme -> Edit Scheme
    環(huán)境變量的設(shè)置.png
  • OBJC_PRINT_LOAD_METHODS為例設(shè)置成YES句柠,打印所有實現(xiàn)了load方法的類
    打印結(jié)果.png
1.2浦译、environ_init()

關(guān)于線程key的綁定

void tls_init(void)
{
#if SUPPORT_DIRECT_THREAD_KEYS
//: -- init方法
    pthread_key_init_np(TLS_DIRECT_KEY, &_objc_pthread_destroyspecific);
#else
//: -- 析構(gòu)
    _objc_pthread_key = tls_create(&_objc_pthread_destroyspecific);
#endif
}
1.3棒假、static_init()

初始化objc庫里面的構(gòu)造函數(shù),比dyld的析構(gòu)c++還早精盅,主動調(diào)用

1.4帽哑、runtime_init()

運行時初始化

void runtime_init(void)
{
//: -- 分類處理的初始化
    objc::unattachedCategories.init(32);
//: -- 初始化class表
    objc::allocatedClasses.init();
}
1.5、exception_init()

初始化libobjc的異常處理系統(tǒng)

  • crash不是崩潰叹俏,是違反系統(tǒng)的規(guī)定妻枕,系統(tǒng)給出的信號
  • 系統(tǒng)會調(diào)用一個默認(rèn)回調(diào)函數(shù),中斷你的進(jìn)程粘驰,即崩潰
  • 我們可以指定一個回調(diào)函數(shù)屡谐,攔截中斷,就可以攔截崩潰
1.6蝌数、cache_init()

緩存條件初始化

1.7愕掏、_imp_implementationWithBlock_init()

通常這不會做什么,因為所有的初始化都是惰性的顶伞,但是對于某些進(jìn)程饵撑,我們會迫不及待地加載trampolines dylib

1.8、_dyld_objc_notify_register(&map_images, load_images, unmap_image)

注冊回調(diào)函數(shù)

二唆貌、dyld和objc關(guān)聯(lián)

  • 我們發(fā)現(xiàn)objc的源碼中滑潘,并沒有實現(xiàn)_dyld_objc_notify_register函數(shù),但是在dyld的源碼中挠锥,我們找到了_dyld_objc_notify_register的實現(xiàn)众羡,這里是跨庫調(diào)用
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    dyld::registerObjCNotifiers(mapped, init, unmapped);
}

map_images:映射鏡像文件,管理文件中和動態(tài)庫中所有的符號
load_images:加載鏡像文件
unmap_image:移除鏡像文件
注意:map_images前面有&蓖租,屬于引用類型粱侣,外界條件變,它就會跟著變蓖宦,要傳遞值
注意:load_images屬于值類型齐婴,不傳遞值

  • 調(diào)用registerObjCNotifiers
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
    }

    // <rdar://problem/32209809> 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());
        }
    }
}

objc把回調(diào)函數(shù)map_imagesload_images稠茂、unmap_image分別傳給了dyldsNotifyObjCMapped柠偶、sNotifyObjCInitsNotifyObjCUnmapped

  • 現(xiàn)在研究sNotifyObjCMapped睬关,sNotifyObjCInit诱担,sNotifyObjCUnmapped是何時被調(diào)用的
  • 全局搜索sNotifyObjCMapped,發(fā)現(xiàn)在notifyBatchPartial方法中調(diào)用了sNotifyObjCMapped
static void notifyBatchPartial(dyld_image_states state, bool orLater, dyld_image_state_change_handler onlyHandler, bool preflightOnly, bool onlyObjCMappedNotification)
{
...
//: -- sNotifyObjCMapped 調(diào)用
(*sNotifyObjCMapped)(objcImageCount, paths, mhs);
...
}

在上面的registerObjCNotifiers方法中就對notifyBatchPartial進(jìn)行了調(diào)用电爹,即objc中調(diào)用_dyld_objc_notify_register進(jìn)入dyld后蔫仙,就會回調(diào)map_images,進(jìn)行鏡像文件的映射

  • 全局搜索sNotifyObjCInit丐箩,發(fā)現(xiàn)在notifySingle方法中調(diào)用了sNotifyObjCInit
static void notifySingle(dyld_image_states state, const ImageLoader* image, ImageLoader::InitializerTimingList* timingInfo)
{
    ...
    if ( (state == dyld_image_state_dependents_initialized) && (sNotifyObjCInit != NULL) && image->notifyObjC() ) {
        uint64_t t0 = mach_absolute_time();
        dyld3::ScopedTimer timer(DBG_DYLD_TIMING_OBJC_INIT, (uint64_t)image->machHeader(), 0, 0);
//: -- sNotifyObjCInit 調(diào)用
        (*sNotifyObjCInit)(image->getRealPath(), image->machHeader());
        uint64_t t1 = mach_absolute_time();
        uint64_t t2 = mach_absolute_time();
        uint64_t timeInObjC = t1-t0;
        uint64_t emptyTime = (t2-t1)*100;
        if ( (timeInObjC > emptyTime) && (timingInfo != NULL) ) {
            timingInfo->addTime(image->getShortName(), timeInObjC);
        }
    }
    ...
}

當(dāng)dyld_image_states == dyld_image_state_dependents_initialized時摇邦,會開始調(diào)用sNotifyObjCInit

  • 在文章開頭的引入中恤煞,我們分析了recursiveInitialization方法,里面調(diào)用了``
void ImageLoader::recursiveInitialization(const LinkContext& context, mach_port_t this_thread, const char* pathToInitialize,
                                          InitializerTimingList& timingInfo, UninitedUpwards& uninitUps)
{
    ...
    // let objc know we are about to initialize this image
    uint64_t t1 = mach_absolute_time();
    fState = dyld_image_state_dependents_initialized;
    oldState = fState;
//: -- 調(diào)用notifySingle施籍,并且`dyld_image_states` == `dyld_image_state_dependents_initialized `
    context.notifySingle(dyld_image_state_dependents_initialized, this, &timingInfo);
            
    // initialize this image
    bool hasInitializers = this->doInitialization(context);

    // let anyone know we finished initializing this image
    fState = dyld_image_state_initialized;
    oldState = fState;
    context.notifySingle(dyld_image_state_initialized, this, NULL);
    ...
}
  • 全局搜索sNotifyObjCUnmapped居扒,發(fā)現(xiàn)在removeImage方法中調(diào)用了sNotifyObjCUnmapped
void removeImage(ImageLoader* image)
{
    ...
    (*sNotifyObjCUnmapped)(image->getRealPath(), image->machHeader());
    ...
}

dyld的加載流程從_dyld_start -> dyldbootstrap -> dyld::_main,在dyld::_main流程的最后一步尋找主程序入口丑慎,進(jìn)入了recursiveInitialization方法喜喂,首先調(diào)用了context.notifySingle方法進(jìn)行單個通知注入,調(diào)用回調(diào)函數(shù)load_images立哑,調(diào)用doInitialization方法的時候夜惭,會從dyld -> libSystem -> libdispatch -> objc中的_objc_init_objc_init又調(diào)用了dyld中實現(xiàn)的_dyld_objc_notify_register注冊回調(diào)函數(shù)铛绰,調(diào)用dyld中的_dyld_objc_notify_register的時候诈茧,又調(diào)用了回調(diào)函數(shù)map_images,這樣就構(gòu)成了跨庫閉環(huán)捂掰,就把objcdyld進(jìn)行了關(guān)聯(lián)敢会。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市这嚣,隨后出現(xiàn)的幾起案子鸥昏,更是在濱河造成了極大的恐慌,老刑警劉巖姐帚,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吏垮,死亡現(xiàn)場離奇詭異,居然都是意外死亡罐旗,警方通過查閱死者的電腦和手機(jī)膳汪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來九秀,“玉大人遗嗽,你說我怎么就攤上這事」难眩” “怎么了痹换?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長都弹。 經(jīng)常有香客問我娇豫,道長,這世上最難降的妖魔是什么畅厢? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任锤躁,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘系羞。我一直安慰自己,他們只是感情好霸琴,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布椒振。 她就那樣靜靜地躺著,像睡著了一般梧乘。 火紅的嫁衣襯著肌膚如雪澎迎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天选调,我揣著相機(jī)與錄音夹供,去河邊找鬼。 笑死仁堪,一個胖子當(dāng)著我的面吹牛哮洽,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弦聂,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼鸟辅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了莺葫?” 一聲冷哼從身側(cè)響起匪凉,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捺檬,沒想到半個月后再层,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡堡纬,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年聂受,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片隐轩。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡饺饭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出职车,到底是詐尸還是另有隱情瘫俊,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布悴灵,位于F島的核電站扛芽,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏积瞒。R本人自食惡果不足惜川尖,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茫孔。 院中可真熱鬧叮喳,春花似錦被芳、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至锣咒,卻和暖如春侵状,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背毅整。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工趣兄, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人悼嫉。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓艇潭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親承粤。 傳聞我的和親對象是個殘疾皇子暴区,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355