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.
該過程又包含若干步驟:
- 獲取鏡像文件中的類列表,遍歷列表進(jìn)行類讀妊客弧(調(diào)用
readClass
)董瞻; - 遍歷注冊所有的
selector
名字(調(diào)用__sel_registerName
); - 遍歷讀取協(xié)議列表(調(diào)用
_getObjc2ProtocolList
,readProtocol
)睬澡; - 遍歷實例化運行時類結(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
賦給本類鲸拥。也就是說初始化讀取時僧免,本類的ro
和rw
是相同的。核心代碼如下:
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í)行步驟有:
- 初始化類結(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;
- 給
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;
- 確定
rw
的版本卫病;
rw->version = isMeta ? 7 : 0; // old runtime went up to 6
- 對超類和元類分別進(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()));
- 初始化本類的isa并關(guān)聯(lián)超類益咬;
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
- 繼承超類的原始變量并初始化本類的變量內(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)存上移動。
- 關(guān)聯(lián)超類的繼承鏈宠互;
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
- 關(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)行完善坞淮。