底層原理:dyld和objc關(guān)聯(lián)

我們?cè)谥暗奶骄窟^程中發(fā)現(xiàn)dyld加載中會(huì)調(diào)用到_objc_init,這篇文章我們從_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();//環(huán)境變量
    tls_init();//線程綁定key
    static_init();//c++靜態(tài)構(gòu)造函數(shù)運(yùn)行
    runtime_init();//運(yùn)行時(shí)初始化
    exception_init();//libobjc異常處理
    cache_init();//緩存初始化
    _imp_implementationWithBlock_init();

    // map_images()  load_images()
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
    didCallDyldNotifyRegister = true;
#endif
}

從_objc_init的源碼分析,主要分成以下部分:

  • environ_init:環(huán)境變量初始化
  • tls_init:關(guān)于線程key的綁定
  • static_init:運(yùn)行C++靜態(tài)構(gòu)造函數(shù)(只會(huì)運(yùn)行系統(tǒng)級(jí)別的構(gòu)造函數(shù)),在dyld調(diào)用靜態(tài)析構(gòu)函數(shù)之前丁侄,libc會(huì)調(diào)用_objc_init
  • runtime_init:runtime運(yùn)行時(shí)環(huán)境初始化,里面操作是unattachedCategories朝巫、allocatedClasses(表的初始化)
  • exception_init:libobjc異常處理初始化
  • cache_init: 緩存初始化
  • _imp_implementationWithBlock_init :?jiǎn)?dòng)回調(diào)機(jī)制鸿摇。
  • _dyld_objc_notify_register: dyld的注冊(cè)
    在這些部分中,包含了各種初始化的操作劈猿,以及最重要的部分_dyld_objc_notify_register拙吉,其中有使用到map_images(處理dyld給的鏡像文件)、load_images(加載映射鏡像文件)揪荣。
    _dyld_objc_notify_register源碼
void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped);

map_images對(duì)應(yīng)mapped筷黔,load_images對(duì)應(yīng)init,unmap_image對(duì)應(yīng)unmapped仗颈。
從_dyld_objc_notify_register方法定義位于dyld_priv.h文件中必逆,其對(duì)應(yīng)的實(shí)現(xiàn)是在dyld的源碼中的,在dyld的源碼中我們找到了對(duì)應(yīng)的處理

void _dyld_objc_notify_register(_dyld_objc_notify_mapped    mapped,
                                _dyld_objc_notify_init      init,
                                _dyld_objc_notify_unmapped  unmapped)
{
    log_apis("_dyld_objc_notify_register(%p, %p, %p)\n", mapped, init, unmapped);

    gAllImages.setObjCNotifiers(mapped, init, unmapped);
}

void AllImages::setObjCNotifiers(_dyld_objc_notify_mapped map, _dyld_objc_notify_init init, _dyld_objc_notify_unmapped unmap)
{
    _objcNotifyMapped   = map;
    _objcNotifyInit     = init;
    _objcNotifyUnmapped = unmap;
  .....
}

從這里我們可以看出揽乱,map_images名眉、load_images、unmap_image的調(diào)用實(shí)現(xiàn)是來自于dyld的凰棉。程序運(yùn)行先是dyld_start鏈接操作损拢,鏈接主程序、執(zhí)行主程序初始化撒犀,所有庫(kù)初始化福压,然后執(zhí)行objc_init函數(shù)掏秩,寫入注冊(cè)函數(shù),通知dyld可以繼續(xù)執(zhí)行其他流程荆姆。

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

map_images會(huì)調(diào)用map_images_nolock蒙幻,map_images_nolock中代碼過長(zhǎng)我們就不一一貼出來分析了,直接看其最關(guān)鍵的代碼

    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

_read_images從源碼分析其實(shí)現(xiàn)

  • 條件控制加載一次胆筒。
  • 修復(fù)selector涉及的混亂問題邮破。
  • 發(fā)現(xiàn)類,修復(fù)未解決類仆救,標(biāo)記捆綁類抒和。
  • 修復(fù)重新映射類
  • 修復(fù)舊的objc_msgSend_fixup調(diào)用
  • 發(fā)現(xiàn)protocols,修復(fù)protocol引用
  • 修復(fù)@protocol引用
  • 分類加載處理
  • 類加載處理
  • 未解析處理的類彤蔽,防止被侵犯摧莽。
    在這過程中,有些比較重要的處理我們需要注意
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            if (hi->hasPreoptimizedSelectors()) 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 sel = sel_registerNameNoLock(name, isBundle);
                if (sels[i] != sel) {
                    sels[i] = sel;
                }
            }
        }
    }

這段源碼中顿痪,sels[i] != sel進(jìn)行帶地址的字符串匹配,兩者字符會(huì)一樣但是地址可能不一樣镊辕。

(lldb) po sels[i]
"class"

(lldb) po sel
"class"

(lldb) p/x sels[i]
(SEL) $3 = 0x000000010045ec5e "class"
(lldb) p/x sel
(SEL) $4 = 0x00007fff77889d1d "class"
(lldb) 
        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,這個(gè)方法會(huì)讀編譯器編寫的類和元類蚁袭。cls在這里會(huì)由一個(gè)地址征懈,經(jīng)過readClass讀取類信息,readClass讀取的流程后續(xù)再進(jìn)行分析撕阎。

接下來看

for (EACH_HEADER) {
        classref_t const *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;

            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't disallow all Swift classes because of
                // classes like Swift.__EmptyArrayStorage
            }
            realizeClassWithoutSwift(cls, nil);
        }
    }

這里是分類的加載處理受裹,具體的加載情況及加載時(shí)機(jī)我們后續(xù)再分析。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末虏束,一起剝皮案震驚了整個(gè)濱河市棉饶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镇匀,老刑警劉巖照藻,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異汗侵,居然都是意外死亡幸缕,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門晰韵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來发乔,“玉大人,你說我怎么就攤上這事雪猪±干校” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵只恨,是天一觀的道長(zhǎng)译仗。 經(jīng)常有香客問我抬虽,道長(zhǎng),這世上最難降的妖魔是什么纵菌? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任阐污,我火速辦了婚禮,結(jié)果婚禮上咱圆,老公的妹妹穿的比我還像新娘笛辟。我一直安慰自己,他們只是感情好闷堡,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布隘膘。 她就那樣靜靜地躺著疑故,像睡著了一般杠览。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上纵势,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天踱阿,我揣著相機(jī)與錄音,去河邊找鬼钦铁。 笑死软舌,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的牛曹。 我是一名探鬼主播佛点,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼黎比!你這毒婦竟也來了超营?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤阅虫,失蹤者是張志新(化名)和其女友劉穎演闭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體颓帝,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡米碰,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了购城。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吕座。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瘪板,靈堂內(nèi)的尸體忽然破棺而出吴趴,到底是詐尸還是另有隱情,我是刑警寧澤篷帅,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布史侣,位于F島的核電站拴泌,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惊橱。R本人自食惡果不足惜蚪腐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望税朴。 院中可真熱鬧回季,春花似錦、人聲如沸正林。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)觅廓。三九已至鼻忠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間杈绸,已是汗流浹背帖蔓。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞳脓,地道東北人塑娇。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像劫侧,于是被迫代替她去往敵國(guó)和親埋酬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345