Objc runtime 初始化過程分析

Bootstrap initialization
/***********************************************************************
* _objc_init
* Bootstrap initialization. Registers our image notifier with dyld.
* Called by libSystem BEFORE library initialization time
**********************************************************************/
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);
}

dyld自舉引導(dǎo)調(diào)用_objc_init甫男,這個函數(shù)很清晰:
首先會進(jìn)行各單位初始化,如環(huán)境變量初始化又跛、tls (thread local storage)線程本地緩存初始化若治、靜態(tài)構(gòu)造器初始化、鎖初始化礼烈、異常處理初始化静暂;
其次要保存映射所有的鏡像文件(map_images)谱秽、保存加載這些鏡像文件函數(shù)指針(load_images)疟赊、保存取消映射的處理函數(shù)指針(unmap_images)。

Map images

這里map_images會調(diào)用map_images_nolock驮审,這個函數(shù)主要會做兩件事:

Selector initialization

一是遍歷當(dāng)前image中所有的class并計算出使用到的所有libobjc庫中的sel的數(shù)量,然后調(diào)用sel_init對所有sel進(jìn)行一次初始化selector table注冊 (Initialize selector tables and register selectors used internally.)地来;

Read images

二是讀入該鏡像文件(_read_images)熙掺,這個函數(shù)會處理當(dāng)前鏡像文件的頭部信息币绩。源碼注釋如下:

Perform initial processing of the headers 
in the linked list beginning with headerList. 

該過程又包含若干步驟:

  1. 獲取鏡像文件中的類列表,遍歷列表進(jìn)行類讀妊客弧(調(diào)用readClass)董瞻;
  2. 遍歷注冊所有的selector名字(調(diào)用__sel_registerName);
  3. 遍歷讀取協(xié)議列表(調(diào)用_getObjc2ProtocolList, readProtocol)睬澡;
  4. 遍歷實例化運行時類結(jié)構(gòu)(調(diào)用realizeClass)煞聪。
    以上是Read images的主要步驟昔脯,省略了一些打印和懸垂指針的判斷條件處理等等笛臣。下面具體分析各個主要函數(shù)做的事情:
Read class
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be: 
* - cls
* - nil  (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)

源碼函數(shù)說明注釋如上,該函數(shù)會初始化類結(jié)構(gòu)體的class_rw_t類型的data (rw)并強制轉(zhuǎn)換成該結(jié)構(gòu)體的class_ro_t類型的數(shù)據(jù)ro静陈;同時把rw賦給本類鲸拥。也就是說初始化讀取時僧免,本類的rorw是相同的。核心代碼如下:

class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro;
memcpy(newCls, cls, sizeof(objc_class));
rw->ro = (class_ro_t *)newCls->data();
newCls->setData(rw);
Realize class
* realizeClass
* Performs first-time initialization on class cls, 
* including allocating its read-write data.
* Returns the real class structure for the class. 

函數(shù)說明注釋中解釋得很清楚撞叨,這個函數(shù)主要是用于執(zhí)行類的第一次初始化,其中包括了給rw數(shù)據(jù)分配內(nèi)存空間胡岔,同時構(gòu)造這個類的結(jié)構(gòu)體枷餐。
從函數(shù)內(nèi)部源碼看到尖淘,具體的執(zhí)行步驟有:

  1. 初始化類結(jié)構(gòu)體的基本變量,如ro(基本只讀數(shù)據(jù)), rw(可讀寫數(shù)據(jù)), supercls(超類), metacls(元類), isMeta(元類標(biāo)志位)等惊暴;
const class_ro_t *ro;
    class_rw_t *rw;
    Class supercls;
    Class metacls;
    bool isMeta;
  1. ro, rw分配內(nèi)存地址趁桃;
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);
    }

    isMeta = ro->flags & RO_META;
  1. 確定rw的版本卫病;
rw->version = isMeta ? 7 : 0;  // old runtime went up to 6
  1. 對超類和元類分別進(jìn)行realize class
// Realize superclass and metaclass, if they aren't already.
    // This needs to be done after RW_REALIZED is set above, for root classes.
    // This needs to be done after class index is chosen, for root metaclasses.
    supercls = realizeClass(remapClass(cls->superclass));
    metacls = realizeClass(remapClass(cls->ISA()));
  1. 初始化本類的isa并關(guān)聯(lián)超類益咬;
// Update superclass and metaclass in case of remapping
    cls->superclass = supercls;
    cls->initClassIsa(metacls);
  1. 繼承超類的原始變量并初始化本類的變量內(nèi)存布局幽告;
//static void reconcileInstanceVariables
//(Class cls, Class supercls, const class_ro_t*& ro) 
// Reconcile instance variable offsets / layout.
    // This may reallocate class_ro_t, updating our ro variable.
    if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);

這里reconcileInstanceVariables發(fā)揮的作用就是確定本類的內(nèi)存布局裆甩。
具體來說,繼承于NSObject的類在不同版本的iOS下應(yīng)該是不會因為系統(tǒng)增加了基類變量而導(dǎo)致子類的內(nèi)存布局出問題的冻河。

比如說1.0的系統(tǒng)NSObject中有n個變量茉帅,他們的內(nèi)存布局是從0-n的一個范圍担敌;子類如果繼承于NSObject并增加了m個自定義的變量廷蓉,理論上這m個變量應(yīng)該是追加在0-n的末尾马昙,變成0-(n+m)行楞。

如果下個版本的NSObject增加了x個變量土匀,基類的內(nèi)存布局變成了0-(n+x),如果runtime不進(jìn)行相應(yīng)的布局調(diào)整证杭,那么子類自定義的m個變量可能就會覆蓋掉系統(tǒng)新增的那x個變量妒御,造成內(nèi)存覆蓋的問題乎莉。

所以這個函數(shù)的作用,就是根據(jù)基類的變量內(nèi)存布局哼鬓,進(jìn)行相應(yīng)的調(diào)整边灭。內(nèi)部會調(diào)用moveIvars來進(jìn)行變量在內(nèi)存上移動。

  1. 關(guān)聯(lián)超類的繼承鏈宠互;
// Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }
  1. 關(guān)聯(lián)類別列表椭坚。
// Attach categories
    methodizeClass(cls);
Methodize class

進(jìn)一步閱讀這個函數(shù)善茎,源碼注釋如下:

* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.

該函數(shù)會對屬性列表、方法列表和協(xié)議列表做一些排序上的調(diào)整烁焙;然后會把外部的類別整合進(jìn)來耕赘,關(guān)鍵代碼如下:

Attach categories
// Attach categories.
    category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
    attachCategories(cls, cats, false /*don't flush caches*/);

查看attachCategories操骡,有如下注釋:

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
static void 
attachCategories(Class cls, category_list *cats, bool flush_caches)

即該函數(shù)會將類別中的方法列表赚窃,屬性和協(xié)議列表分別都加入本類中岔激,并假定了類別列表加載的順序是根據(jù)類別文件的加載順序虑鼎。
注意其中加載方法列表的代碼如下:

rw->methods.attachLists(mlists, mcount);
Attach category method lists

attachLists的關(guān)鍵實現(xiàn)如下:

array()->count = newCount;
memmove(array()->lists + addedCount, 
        array()->lists, oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, 
       addedLists, addedCount * sizeof(array()->lists[0]));

可以看到本類在加載類別中的方法列表時會先使用memmove將本類的原始方法所占內(nèi)存向后移動類別方法所需的空間偏移,然后再用memcpy將類別方法復(fù)制到本類的方法列表的頭部空間位置匾七。這一點的運行時表現(xiàn)就是當(dāng)我們使用類別復(fù)寫了類的一個已有方法時乐尊,優(yōu)先會調(diào)用的是類別復(fù)寫的方法而不是已有方法划址。此時如果想在類別存在同名方法的前提下依然調(diào)用原方法,則可以利用運行時獲取方法列表痢缎,從列表尾部往回遍歷世澜,找到第一個SEL相同的方法指針并執(zhí)行即可。

注意到嵌洼,這個runtime初始化過程不包含任何類的initialization封恰,而僅僅是做了運行時類結(jié)構(gòu)體的一些準(zhǔn)備工作诺舔。這是因為類的initialization (_class_initialize)會發(fā)生在對某個類或類實例第一次發(fā)送消息時完成,也就是在_class_lookupMethodAndLoadCache3 -> lookUpImpOrForward中完成许昨,同時會發(fā)送+initialize消息給復(fù)寫方法褥赊。這是消息發(fā)送流程,在此不具體說明速那。

至此,Objc runtime初始化的一些關(guān)鍵步驟就分析完了,還有一些細(xì)節(jié)部分沒有覆蓋榆俺,有時間會繼續(xù)進(jìn)行完善坞淮。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末回窘,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子烁涌,更是在濱河造成了極大的恐慌酒觅,老刑警劉巖舷丹,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颜凯,死亡現(xiàn)場離奇詭異,居然都是意外死亡症概,警方通過查閱死者的電腦和手機彼城,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來秤涩,“玉大人司抱,你說我怎么就攤上這事习柠≌掌澹” “怎么了武翎?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵宝恶,是天一觀的道長垫毙。 經(jīng)常有香客問我,道長丽蝎,這世上最難降的妖魔是什么膀藐? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任额各,我火速辦了婚禮,結(jié)果婚禮上蛉加,老公的妹妹穿的比我還像新娘缸逃。我一直安慰自己,他們只是感情好丁眼,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布苞七。 她就那樣靜靜地躺著挪丢,像睡著了一般乾蓬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天,我揣著相機與錄音趋距,去河邊找鬼节腐。 笑死,一個胖子當(dāng)著我的面吹牛门怪,可吹牛的內(nèi)容都是我干的锅纺。 我是一名探鬼主播肋殴,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼护锤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了驱入?” 一聲冷哼從身側(cè)響起亏较,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤掩缓,失蹤者是張志新(化名)和其女友劉穎你辣,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宴凉,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡跪解,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了窘行。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片图仓。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡救崔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纬黎,到底是詐尸還是另有隱情劫窒,我是刑警寧澤主巍,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布孕索,位于F島的核電站,受9級特大地震影響搞旭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肄渗,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一镇眷、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恳啥,春花似錦偏灿、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至硝桩,卻和暖如春沿猜,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背碗脊。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人祈坠。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓害碾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親赦拘。 傳聞我的和親對象是個殘疾皇子慌随,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,690評論 0 9
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,093評論 1 32
  • runtime 介紹 Objective-C 是一門動態(tài)性比較強的編程語言躺同,跟 C阁猜、C++ 等語言有著很大的不同,...
    666真666閱讀 902評論 0 3
  • 填滿的 填不飽的是記憶深處 打斷的 打不散的是深情款款 揮灑自如的是 娓娓道來的擁抱之后 的別離
    云舞閱讀 234評論 0 0
  • 干畫技法蹋艺、寶虹細(xì)紋300g剃袍、高光運用留白液。 1.構(gòu)圖捎谨,將幾塊明顯的肉畫出細(xì)節(jié)民效。 2.留白液將高光畫出來。 3.橙...
    可愛的小豬琳琳閱讀 1,058評論 5 20