在 iOS 應(yīng)用程序加載 一篇怎棱,我們得知旁赊,app
由內(nèi)核引導(dǎo)啟動(dòng),之后交由dyld
主導(dǎo)绞蹦,完成運(yùn)行環(huán)境的初始化力奋,配合ImageLoader
將二進(jìn)制文件按格式加載到內(nèi)存,動(dòng)態(tài)鏈接依賴庫(kù)幽七,并由runtime
負(fù)責(zé)加載成objc
定義的結(jié)構(gòu)景殷,所有初始化工作結(jié)束之后,dyld
調(diào)用應(yīng)用程序的main
函數(shù)澡屡。
其中猿挚,dyld
與 objc
互相配合,dyld
加載動(dòng)態(tài)庫(kù)的過(guò)程中初始化 objc
驶鹉,objc
在初始化的過(guò)程中注冊(cè)回調(diào)函數(shù) _dyld_objc_notify_register
绩蜻,通知dyld
執(zhí)行 map_images
,load_images
室埋,unmap_images
以完成映射办绝、載入等過(guò)程(詳細(xì)過(guò)程參見(jiàn) iOS 應(yīng)用程序加載 )。
這一設(shè)計(jì)是很有必要的姚淆,一來(lái)分工明確孕蝉,各司其職;二來(lái)runtime
接管了這部分工作腌逢,可以實(shí)現(xiàn)runtime
的動(dòng)態(tài)性降淮。接下來(lái)我們結(jié)合 libobjc
的初始化來(lái)探究 iOS 類的加載過(guò)程。
0 . 從 _objc_init 開(kāi)始
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(); // 讀取影響運(yùn)行時(shí)的環(huán)境變量
tls_init(); // 處理線程key的綁定
static_init(); // 運(yùn)行C++靜態(tài)構(gòu)造函數(shù)
runtime_init(); // runtime運(yùn)行時(shí)環(huán)境初始化,里面主要是:unattachedCategories,allocatedClasses
exception_init(); // 初始化libobjc的異常處理系統(tǒng)
cache_init(); // 緩存條件初始化
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
在任何 image
初始化之前搏讶,要先確保 libSystem_initializer
的正常執(zhí)行佳鳖,它是優(yōu)先級(jí)最高的,必須先保證系統(tǒng)庫(kù)的正常初始化媒惕,之后 經(jīng)由 libdispatch_init
--> _os_object_init
繼而調(diào)用 _objc_init
系吩。
在 _objc_init 中:
environ_init()
讀取影響運(yùn)行時(shí)的環(huán)境變量.tls_init()
處理線程key的綁定,處理每個(gè)線程數(shù)據(jù)的析構(gòu)函數(shù).static_init()
運(yùn)行C ++靜態(tài)構(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(用objc_allocateClassPair分配的所有類(和元類)的表) 兩個(gè)表的創(chuàng)建.exception_init()
初始化libobjc的異常處理系統(tǒng)以監(jiān)測(cè)異常.cache_init()
緩存條件初始化_imp_implementationWithBlock_init()
啟動(dòng) trampoline machinery絮蒿。通常這不會(huì)做什么尊搬,因?yàn)樗械某跏蓟际嵌栊缘模菍?duì)于某些進(jìn)程土涝,我們會(huì)迫不及待地加載trampolines dylib佛寿。
_objc_init 完成上述的 init 工作之后,通知 dyld
在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用 map_images
, load_images
, unmap_image
函數(shù)但壮。
1. map_images
map_images
的工作是:處理由 dyld
映射的給定鏡像冀泻。負(fù)責(zé)管理文件和動(dòng)態(tài)庫(kù)中所有的符號(hào)(class、selector蜡饵、protocol弹渔、category 等)
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
函數(shù)中調(diào)用 map_images_nolock
,(這里只關(guān)注讀取鏡像的邏輯溯祸,所以做了一些源碼的省略處理肢专。)
void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
const struct mach_header * const mhdrs[])
{
......
// 從objc的元數(shù)據(jù)查找所有的鏡像
......
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
for (auto func : loadImageFuncs) {
for (uint32_t i = 0; i < mhCount; i++) {
func(mhdrs[I]);
}
}
}
從主程序的 【machO】 中找到所有的鏡像文件,調(diào)用 _read_images
焦辅,執(zhí)行所有的類注冊(cè)和修復(fù)等功能博杖。在所有設(shè)置完成后調(diào)用鏡像加載。那么在加載鏡像之前的操作就聚集在了 _read_images
里面了筷登,這也是我們重點(diǎn)研究的對(duì)象剃根。源碼很長(zhǎng),精簡(jiǎn)如下:
1.1 _read_images
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
......
// ① 控制進(jìn)行只做一次加載(建表:未在dyld共享緩存中的已命名類的列表前方,無(wú)論是否實(shí)現(xiàn))
if (!doneOnce) {
doneOnce = YES;
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
}
......
// ② 修復(fù)@selector 引用(修復(fù)預(yù)編譯階段的 `@selector` 的混亂問(wèn)題)
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;
}
}
}
}
// ③ 修復(fù)未解析的 future class狈醉, 標(biāo)記 Bundle class
for (EACH_HEADER) {
classref_t const *classlist = _getObjc2ClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = (Class)classlist[I];
Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
if (newCls != cls && newCls) {
resolvedFutureClasses = (Class *)
realloc(resolvedFutureClasses,
(resolvedFutureClassCount+1) * sizeof(Class));
resolvedFutureClasses[resolvedFutureClassCount++] = newCls;
}
}
}
// ④ 修復(fù)重映射的類;(一些沒(méi)有被鏡像文件加載進(jìn)來(lái)的類)
if (!noClassesRemapped()) {
for (EACH_HEADER) {
Class *classrefs = _getObjc2ClassRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
classrefs = _getObjc2SuperRefs(hi, &count);
for (i = 0; i < count; i++) {
remapClassRef(&classrefs[I]);
}
}
}
#if SUPPORT_FIXUP
// ⑤ Fix up old objc_msgSend_fixup call sites
for (EACH_HEADER) {
message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
if (count == 0) continue;
if (PrintVtables) {
_objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
"call sites in %s", count, hi->fname());
}
for (i = 0; i < count; i++) {
fixupMessageRef(refs+i);
}
}
#endif
// ⑥ readProtocol
NXMapTable *protocol_map = protocols();
protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
for (EACH_HEADER) {
for (i = 0; i < count; i++) {
readProtocol(protolist[i], cls, protocol_map,
isPreoptimized, isBundle);
}
}
// ⑦ Fix up @protocol references
for (EACH_HEADER) {
if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
continue;
protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
for (i = 0; i < count; i++) {
remapProtocolRef(&protolist[I]);
}
}
// ⑧ load_categories_nolock
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
// ⑨ non-lazy 類的加載
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);
realizeClassWithoutSwift(cls, nil);
}
}
// ⑩ realize future classes (沒(méi)有被處理的類镣丑,)
// Realize newly-resolved future classes, in case CF manipulates them 實(shí)現(xiàn)新解析的未來(lái)類舔糖,以防CF操作它們
if (resolvedFutureClasses) {
for (i = 0; i < resolvedFutureClassCount; i++) {
Class cls = resolvedFutureClasses[I];
if (cls->isSwiftStable()) {
_objc_fatal("Swift class is not allowed to be future");
}
realizeClassWithoutSwift(cls, nil);
cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
}
free(resolvedFutureClasses);
}
if (DebugNonFragileIvars) {
realizeAllClasses();
}
}
簡(jiǎn)化總結(jié)如下:
- ①:條件控制只做一次:
建表gdb_objc_realized_classes
娱两,存儲(chǔ)未在dyld共享緩存中的已命名類的列表莺匠,無(wú)論是否實(shí)現(xiàn)- ②:修復(fù)@selector 引用:
修復(fù)預(yù)編譯階段 @selector 的混亂問(wèn)題,我們知道十兢,SEL 是一個(gè)帶地址的字符串趣竣,盡管兩個(gè)方法的名字相同,但方法的地址未必相同旱物。如上遥缕,可能多個(gè)框架都會(huì)有init方法
,在系統(tǒng)中讀取方法的地址是按照框架在主程中的偏移以及方法在框架內(nèi)的偏移來(lái)定位的宵呛。因此单匣,我們需要對(duì)@selector進(jìn)行適當(dāng)調(diào)整。- ③:修復(fù)未解析的 future classes.
從【MachO】__objc_classlist
section中加載類列表,遍歷列表中的類户秤,進(jìn)行readClass
码秉,如果readClass
的結(jié)果與列表中的類不同,則進(jìn)行修復(fù)操作鸡号,但這一般不會(huì)出現(xiàn)转砖,只有類被移動(dòng)并且沒(méi)有被刪除才會(huì)出現(xiàn)。在readClass
中鲸伴,從cls->mangledName()
中取到類的名字府蔗,將類的名字與地址關(guān)聯(lián),插入到第①步創(chuàng)建的gdb_objc_realized_classes
中汞窗,同時(shí)將該類以及元類插入到allocatedClasses
表姓赤。
在這一步中,readClass
將類的地址和名字
加載到了內(nèi)存中仲吏。- ④:修復(fù)重映射的類:
如果存在沒(méi)有被鏡像文件加載進(jìn)來(lái)的類模捂,則在此時(shí)進(jìn)行重映射。- ⑤:修復(fù)一些舊的
objc_msgSend_fixup
調(diào)用
主要對(duì)一些舊的消息修復(fù)進(jìn)行強(qiáng)制修復(fù)工作蜘矢,如alloc -> objc_alloc狂男、allocWithZone -> objc_allocWithZone 等。- ⑥:readProtocol:
創(chuàng)建存儲(chǔ)proctol的哈希表品腹,從【MachO】__objc_protolist
section中讀取協(xié)議列表岖食,遍歷協(xié)議列表,進(jìn)行readProtocol
舞吭,將協(xié)議添加到proctol表泡垃,注冊(cè)到內(nèi)存。- ⑦: Fix up @protocol references
預(yù)先優(yōu)化的鏡像可能已經(jīng)有了正確的protocol引用羡鸥,但是我們并不能確定蔑穴,所以這里進(jìn)行一次修復(fù)工作,以防被引用的協(xié)議被重新分配惧浴。- ⑧:load_categories
加載分類存和,需要注意的是,這里并不會(huì)加載分類衷旅,只有在didInitialAttachCategories
賦值為true之后才會(huì)執(zhí)行捐腿,而didInitialAttachCategories
賦值為true的過(guò)程是在_dyld_objc_notify_register
的調(diào)用完成后的第一個(gè)load_images
調(diào)用時(shí)賦值的。- ⑨:non-lazy 類的加載
這里完成的是非懶加載的類的實(shí)現(xiàn)柿顶,也就是實(shí)現(xiàn)了+load
方法的類茄袖。
從【MachO】__objc_nlclslist
section中讀取非懶加載類列表,執(zhí)行addClassTableEntry(cls);
將非懶加載類插入到類表嘁锯,加載到內(nèi)存宪祥。在第③步中聂薪,我們加載到內(nèi)存的類已有了地址和名字
,最后執(zhí)行realizeClassWithoutSwift
對(duì)類的結(jié)構(gòu)進(jìn)行完善蝗羊。- ⑩:realize future classes
如果存在被處理的 future class 胆建,則需要在這里實(shí)現(xiàn),以防CF操作它們肘交。這里的實(shí)現(xiàn)也是通過(guò)realizeClassWithoutSwift
笆载。
_read_images
大致做了上述這些事情,如果只關(guān)注類的加載過(guò)程的話涯呻,可以作如下簡(jiǎn)化理解:
- 1.建表
gdb_objc_realized_classes
創(chuàng)建一個(gè)不在共享緩存的已命名類的表凉驻,不關(guān)乎這個(gè)類是否實(shí)現(xiàn)。 - 2.
readClass
:
從【MachO】__objc_classlist
section 中讀取 類的列表數(shù)據(jù)獲取類的地址信息复罐,將類的名字寫(xiě)入涝登,插入gdb_objc_realized_classes
表,并且插入allocatedClasses
表效诅。自此胀滚,類被載入內(nèi)存,并且有了地址與名字
乱投。 - 3.
realizeClassWithoutSwift
實(shí)現(xiàn)類的細(xì)節(jié)(ro
咽笼、rw
等處理),保證類的結(jié)構(gòu)的完整性戚炫。
具體的readClass
和 realizeClassWithoutSwift
代碼分析如下:
1.1.1 readClass
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
const char *mangledName = cls->mangledName();
// 如果繼承鏈中存在父類缺失或者weak-linked情況剑刑,直接忽略這個(gè)類, return nil
if (missingWeakSuperclass(cls)) {
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING class '%s' with "
"missing weak-linked superclass",
cls->nameForLogging());
}
addRemappedClass(cls, nil);
cls->superclass = nil;
return nil;
}
cls->fixupBackwardDeployingStableSwift();
Class replacing = nil;
// 如果這個(gè)類是一個(gè)早先分配的作為將來(lái)要處理的類双肤,那么將objc_class數(shù)據(jù)復(fù)制到future class施掏,保存future的rw數(shù)據(jù)塊
if (Class newCls = popFutureNamedClass(mangledName)) {
if (newCls->isAnySwift()) {
_objc_fatal("Can't complete future class request for '%s' "
"because the real class is too big.",
cls->nameForLogging());
}
class_rw_t *rw = newCls->data();
const class_ro_t *old_ro = rw->ro();
memcpy(newCls, cls, sizeof(objc_class));
rw->set_ro((class_ro_t *)newCls->data());
newCls->setData(rw);
freeIfMutable((char *)old_ro->name);
free((void *)old_ro);
addRemappedClass(cls, newCls);
replacing = cls;
cls = newCls;
}
// 如果是預(yù)優(yōu)化的類 并且 不是future class, 則 ASSERT茅糜,
if (headerIsPreoptimized && !replacing) {
ASSERT(getClassExceptSomeSwift(mangledName));
} else {
// 關(guān)聯(lián)地址與名字 加入gdb_objc_realized_classes表
addNamedClass(cls, mangledName, replacing);
// 加入allocatedClasses表
addClassTableEntry(cls);
}
// for future reference: shared cache never contains MH_BUNDLEs
if (headerIsBundle) {
cls->data()->flags |= RO_FROM_BUNDLE;
cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
}
return cls;
}
readClass
處理:
- 1.從
cls->mangledName()
獲取類的名字 - 2.如果繼承鏈中存在父類缺失或者weak-linked情況七芭,直接忽略這個(gè)類(這個(gè)類是不完整的), return nil蔑赘;
- 3.如果這個(gè)類是一個(gè)早先分配的作為將來(lái)要處理的類狸驳,那么將objc_class數(shù)據(jù)復(fù)制到future class,保存future的
rw
數(shù)據(jù)塊; - 4.如果是預(yù)優(yōu)化的類 并且 不是future class米死, 則 ASSERT锌历;否則
執(zhí)行addNamedClass
關(guān)聯(lián)類的地址與名字 ,插入gdb_objc_realized_classes
表.
執(zhí)行addClassTableEntry
插入allocatedClasses
表;
1.1.1.1 addNamedClass
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));
}
判斷gdb_objc_realized_classes
表中是否已經(jīng)存在該名稱的類税灌,如果存在柴钻,插入nonMetaClasses
表隐轩。如果不存在插入gdb_objc_realized_classes
表。
1.1.1.2 addClassTableEntry
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
如果該類是在運(yùn)行時(shí)已知的類(如位于共享緩存中物喷,在一個(gè)已加載鏡像的數(shù)據(jù)段中卤材,或者已經(jīng)用obj_allocateClassPair分配),則不需要插入表峦失,否則插入allocatedClasses
表扇丛,同時(shí)將類的元類也插入表中。allocatedClasses
表是在runtime_init()
時(shí)創(chuàng)建的.
至此尉辑,【MachO】中的類已載入內(nèi)存帆精,并且有了名字和地址,但是數(shù)據(jù)還沒(méi)有關(guān)聯(lián)隧魄,而數(shù)據(jù)的關(guān)聯(lián)是在realizeClassWithoutSwift
中進(jìn)行的卓练。
1.1.2 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()) return cls; // 如果類已實(shí)現(xiàn),則直接返回
ASSERT(cls == remapClass(cls));
auto ro = (const class_ro_t *)cls->data(); // 從data()中取類的信息
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
#if FAST_CACHE_META
if (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif
cls->chooseClassArrayIndex();
if (PrintConnecting) {
_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
cls->nameForLogging(), isMeta ? " (meta)" : "",
(void*)cls, ro, cls->classArrayIndex(),
cls->isSwiftStable() ? "(swift)" : "",
cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
}
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
#if SUPPORT_NONPOINTER_ISA
...... NONPOINTER_ISA的一些處理
#endif
// Update superclass and metaclass in case of remapping
cls->superclass = supercls;
cls->initClassIsa(metacls);
if (supercls && !isMeta) reconcileInstanceVariables(cls, supercls, ro);
// Set fastInstanceSize if it wasn't set already.
cls->setInstanceSize(ro->instanceSize);
// Copy some flags from ro to rw
if (ro->flags & RO_HAS_CXX_STRUCTORS) {
cls->setHasCxxDtor();
if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
cls->setHasCxxCtor();
}
}
if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
(supercls && supercls->forbidsAssociatedObjects()))
{
rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
}
// Connect this class to its superclass's subclass lists
if (supercls) {
addSubclass(supercls, cls);
} else {
addRootClass(cls);
}
// Attach categories
methodizeClass(cls, previously);
return cls;
}
將realizeClassWithoutSwift
做簡(jiǎn)化理解如下:
從
cls->data()
中讀取類的信息购啄,如果是 future class 襟企,對(duì)rw
、ro
賦值狮含;如果是正常的類顽悼,則開(kāi)辟rw
空間,對(duì)ro
進(jìn)行賦值几迄,并拷貝到rw
中蔚龙。遞歸實(shí)現(xiàn)類的父類以及元類,隨后更新該類的父類和元類以備重新映射映胁。確保繼承鏈以及isa鏈的完整性。
從
ro
中賦值一些標(biāo)志到rw
中屿愚。如果父類存在汇跨,則將這個(gè)類鏈接到它的父類的子類列表,否則作為一個(gè)新的根類妆距。
執(zhí)行
methodizeClass
1.1.2.1 methodizeClass
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name));
}
ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name);
}
#endif
}
methodizeClass
方法中穷遂,主要做了這些事情:
- ① install 類的方法列表
(method_list_t)
;- ② install 類的屬性列表
(property_list_t)
娱据;- ③ install 類的協(xié)議列表
(protocol_list_t)
蚪黑;- ④ Attach categories.
- ① install 類的方法列表
(method_list_t)
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 1);
}
從ro
中讀取baseMethods()
,如果列表有值中剩,通過(guò)prepareMethodLists
對(duì)方法列表進(jìn)行預(yù)處理
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle)
{
runtimeLock.assertLocked();
if (addedCount == 0) return;
if (baseMethods) {
ASSERT(cls->hasCustomAWZ() && cls->hasCustomRR() && cls->hasCustomCore());
}
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[I];
ASSERT(mlist);
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
if (cls->isInitialized()) {
objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);
objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);
}
}
對(duì)于某些類的方法會(huì)存在 RR/AWZ/Core
這樣的特殊情況忌穿,但是我們不需要做任何處理,因?yàn)槟J(rèn)的 RR/AWZ/Core
是不會(huì)在setInitialized()
方法執(zhí)行之前設(shè)置的结啼。
之后通過(guò)fixupMethodList
對(duì)方法列表進(jìn)行排序掠剑,這也是我們?cè)谶M(jìn)行方法查找流程中對(duì)方法列表進(jìn)行二分查找的前提:保證方法列表中的方法是有序的。
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// Unique selectors in list.
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
meth.name = sel_registerNameNoLock(name, bundleCopy);
}
}
// Sort by selector address.
if (sort) {
method_t::SortBySELAddress sorter;
std::stable_sort(mlist->begin(), mlist->end(), sorter);
}
// Mark method list as uniqued and sorted
mlist->setFixedUp();
}
fixup
做了兩件事:Unique
和 Sort
.
Unique:
保證方法的唯一性郊愧;
Sort:
對(duì)方法列表進(jìn)行排序:排序的過(guò)程是根據(jù)方法的name
進(jìn)行排序朴译;struct SortBySELAddress : public std::binary_function<const method_t&, const method_t&, bool> { bool operator() (const method_t& lhs, const method_t& rhs) { return lhs.name < rhs.name; } };
最后標(biāo)記方法列表為已修復(fù)狀態(tài)井佑,后續(xù)不需再進(jìn)行
fixup
了。
prepareMethodLists
的工作完成后眠寿,判斷rwe
是否存在躬翁,如果存在,則rwe->methods.attachLists
盯拱。而此時(shí)rwe
是不存在的盒发,這里補(bǔ)充說(shuō)明一下:
Tips:
在 iOS 類的結(jié)構(gòu)分析 這篇文章中,我們已知:類在編譯期時(shí)狡逢,類的一些數(shù)據(jù)信息保存在
ro
中宁舰,包含了類的名稱,方法甚侣,協(xié)議明吩,實(shí)例變量等編譯期確定
的信息。當(dāng)類被Runtime加載之后殷费,runtime會(huì)為它分配額外的用于 讀取/寫(xiě)入 的rw
印荔。
ro
是只讀的,存放的是編譯期間就確定
的字段信息详羡;而rw
是在 runtime 時(shí)才創(chuàng)建的仍律,它會(huì)先將ro
的內(nèi)容拷貝一份,再將類的分類实柠、屬性水泉、方法、協(xié)議等信息添加進(jìn)去窒盐。而對(duì)于存在動(dòng)態(tài)更改行為的類草则,會(huì)將這部分動(dòng)態(tài)的內(nèi)容提取到
rwe
中。
到目前為止蟹漓,類的加載過(guò)程炕横,對(duì)于 ro
, rw
均已賦值完畢,而 rwe
的賦值是要在類存在動(dòng)態(tài)更改行為時(shí)觸發(fā)的葡粒。此時(shí)rwe
還不存在份殿,所以并不需要rwe->methods.attachLists
。那么rwe
是何時(shí)被賦值的呢嗽交?
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>();
} else {
return extAlloc(v.get<const class_ro_t *>());
}
}
rwe
的創(chuàng)建是通過(guò)extAllocIfNeeded
卿嘲。搜索extAllocIfNeeded
可得到下面的結(jié)果。
分別對(duì)應(yīng)了:attachCategories
夫壁, demangledName
拾枣,class_setVersion
,addMethod
掌唾,addMethods
放前,class_addProtocol
忿磅,_class_addProperty
糯彬,objc_duplicateClass
凭语。無(wú)一不是動(dòng)態(tài)修改的行為。
如果rwe
已創(chuàng)建撩扒,且方法列表有值似扔,attachLists(&list, 1)
- ② install 類的屬性列表
(property_list_t)
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
如果rwe
已創(chuàng)建,且屬性列表有值搓谆,attachLists(&proplist, 1)
- ③ install 類的協(xié)議列表
(protocol_list_t)
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
如果rwe
已創(chuàng)建炒辉,且協(xié)議列表有值,attachLists(&protolist, 1)
可見(jiàn)只要滿足條件泉手,都會(huì)執(zhí)行attachLists
函數(shù)
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
attachLists
函數(shù)區(qū)分了三種情況黔寇,我們按照?qǐng)?zhí)行流程分析如下:
- 【第一種】0 lists --> 1 list
從無(wú)到有的過(guò)程,lists
中還未有任何內(nèi)容斩萌,此時(shí)直接將 new list 內(nèi)容賦值過(guò)來(lái)即可缝裤。- 【第二種】1 list -> many lists
此時(shí)lists
中已有【第一種】 中賦值的數(shù)組。要添加的大小與原大小相加計(jì)算出所需容量大小颊郎,根據(jù)容量開(kāi)辟array()
空間并賦值:將舊的內(nèi)容(oldList
)放在數(shù)組末尾憋飞,新的內(nèi)容(addedLists
)從lists
的首地址開(kāi)始,大小為addedCount
個(gè)空間姆吭,拷貝到lists
中榛做,這樣,舊數(shù)據(jù)放最后内狸,新數(shù)據(jù)整體放在前检眯。- 【第三種】many lists -> many lists
在 【第二種】 中,array()
已賦值昆淡,所以當(dāng)有新的數(shù)組attach
時(shí)锰瘸,重新開(kāi)辟array()
空間,大小依然是舊數(shù)組容量與新數(shù)據(jù)容量之和瘪撇。將舊數(shù)據(jù)移動(dòng)到最后获茬,新數(shù)據(jù)整體拷貝到舊數(shù)據(jù)的前面。
- ④ Attach categories
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
Attach categories
是添加分類的過(guò)程倔既,此部分內(nèi)容放在 iOS 分類詳解 中探討恕曲。
至此為止,類的加載過(guò)程(除分類以外)大致完成渤涌,通過(guò)上面這些步驟佩谣,類的結(jié)構(gòu)已大致完備。
通過(guò) readClass
將類的地址與名字
進(jìn)行關(guān)聯(lián)并且載入內(nèi)存实蓬;
通過(guò) realizeClassWithoutSwift
對(duì)類的 ro
茸俭,rw
完成賦值吊履;
通過(guò) methodizeClass
對(duì)類的方法、屬性调鬓、協(xié)議進(jìn)行完備艇炎,并且完成分類的加載。
當(dāng)存在動(dòng)態(tài)修改等行為時(shí)腾窝,也會(huì)將類的動(dòng)態(tài)修改部分存入rwe
缀踪。
這些都是在 map_images
中完成的。下面我們接著研究load_images
虹脯。
2. load_images
當(dāng)鏡像文件映射完畢后驴娃,會(huì)繼而執(zhí)行 load_images
,處理在dyld
中已映射完畢的鏡像的 +load
方法.
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
首先循集,如果 libobjc.A.dylib 動(dòng)態(tài)庫(kù)的鏡像文件映射完畢且首次執(zhí)行load_images
的時(shí)候唇敞,會(huì)調(diào)用loadAllCategories()
函數(shù),完成所有分類的加載咒彤。(分類相關(guān)的內(nèi)容本章暫不探討疆柔,放在 iOS分類詳解 里統(tǒng)一探討);
系統(tǒng)依賴的動(dòng)態(tài)庫(kù)的很多蔼紧,每一個(gè)動(dòng)態(tài)庫(kù)在執(zhí)行load_images
函數(shù)的時(shí)候都會(huì)判斷當(dāng)前動(dòng)態(tài)庫(kù)的【MachO】中__objc_nlclslist
和__objc_nlcatlist
列表是否有內(nèi)容婆硬。沒(méi)有直接return。如果有奸例,執(zhí)行prepare_load_methods
2.1 prepare_load_methods
void prepare_load_methods(const headerType *mhdr)
{
size_t count, I;
runtimeLock.assertLocked();
// 類的load
classref_t const *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// 分類的load
category_t * const *categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[I];
Class cls = remapClass(cat->cls);
if (!cls) continue; // category for ignored weak-linked class
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls, nil);
ASSERT(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
prepare_load_methods
做了兩件事:
- 從當(dāng)前動(dòng)態(tài)庫(kù)的【MachO】header中讀取
__objc_nlclslist
彬犯,執(zhí)行schedule_class_load
;
- 從當(dāng)前動(dòng)態(tài)庫(kù)的【MachO】header中讀取
__objc_nlcatlist
,隨后迫使主類實(shí)現(xiàn)查吊,繼而執(zhí)行add_category_to_loadable_list
谐区,將待執(zhí)行+load
的分類添加到loadable_categories
表中。
2.1.1 schedule_class_load
static void schedule_class_load(Class cls)
{
if (!cls) return;
ASSERT(cls->isRealized()); // _read_images should realize
// 如已執(zhí)行add_class_to_loadable_list 則不需繼續(xù)添加
if (cls->data()->flags & RW_LOADED) return;
// 確保父類先執(zhí)行
schedule_class_load(cls->superclass);
// 插表
add_class_to_loadable_list(cls);
// 設(shè)置標(biāo)記:cls已執(zhí)行add_class_to_loadable_list
cls->setInfo(RW_LOADED);
}
對(duì)于不存在的類或者未實(shí)現(xiàn)的類進(jìn)行攔截逻卖,同時(shí)對(duì)于已執(zhí)行過(guò)add_class_to_loadable_list
函數(shù)的類也進(jìn)行過(guò)濾宋列。當(dāng)滿足條件向下執(zhí)行的時(shí)候,要先確保該類的父類先于該類執(zhí)行+load
(如果父類實(shí)現(xiàn)了+load
)评也,隨后執(zhí)行add_class_to_loadable_list
插表操作炼杖,并且標(biāo)記cls
為已完成插表操作。
2.1.1.1 add_class_to_loadable_list
void add_class_to_loadable_list(Class cls)
{
IMP method;
loadMethodLock.assertLocked();
// 取到要存入loadable_classes表的類 +load方法的 IMP
method = cls->getLoadMethod();
if (!method) return; // Don't bother if cls has no +load method
if (PrintLoading) {
_objc_inform("LOAD: class '%s' scheduled for +load",
cls->nameForLogging());
}
// 空表或者達(dá)最大容量盗迟,則開(kāi)辟或擴(kuò)容 坤邪,每個(gè)動(dòng)態(tài)庫(kù)中的所有+load方法會(huì)存儲(chǔ)在同一個(gè)表中
if (loadable_classes_used == loadable_classes_allocated) {
loadable_classes_allocated = loadable_classes_allocated*2 + 16;
loadable_classes = (struct loadable_class *)
realloc(loadable_classes,
loadable_classes_allocated *
sizeof(struct loadable_class));
}
loadable_classes[loadable_classes_used].cls = cls;
loadable_classes[loadable_classes_used].method = method;
loadable_classes_used++;
}
通過(guò)getLoadMethod()
取cls
中的ro()->baseMethods()
,遍歷查詢load
方法罚缕,找到則返回方法的 IMP艇纺,執(zhí)行插表操作,未找到直接return
.
每個(gè)動(dòng)態(tài)庫(kù)中需要執(zhí)行+load
的類會(huì)存入到同一張表loadable_classes
,根據(jù)已占用空間和開(kāi)辟空間進(jìn)行對(duì)比黔衡,以判斷是夠需要擴(kuò)容重新開(kāi)辟空間蚓聘。
2.1.2 add_category_to_loadable_list
void add_category_to_loadable_list(Category cat)
{
IMP method;
loadMethodLock.assertLocked();
method = _category_getLoadMethod(cat);
// Don't bother if cat has no +load method
if (!method) return;
if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load",
_category_getClassName(cat), _category_getName(cat));
}
if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}
loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}
分類和主類的插表操作大致相同的,通過(guò)_category_getLoadMethod
從分類的cat->classMethods
中查找+load
方法盟劫,找到則插入loadable_categories
表夜牡。
這里需要注意的是在執(zhí)行add_category_to_loadable_list
操作之前,先要完成主類的實(shí)現(xiàn)realizeClassWithoutSwift
捞高。
2.2 call_load_methods()
prepare_load_methods
函數(shù)氯材,將類的+load渣锦,父類的+load硝岗,分類的+load,都以準(zhǔn)備完畢袋毙,接下來(lái)就要執(zhí)行call_load_methods()
型檀。
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
進(jìn)入do....while
循環(huán),從先前存入的表數(shù)據(jù)loadable_classes
中取出可執(zhí)行+load
方法的類數(shù)據(jù)听盖,進(jìn)行消息發(fā)送胀溺,執(zhí)行+load
方法,從loadable_categories
表中取出可執(zhí)行+load
方法的分類數(shù)據(jù)皆看,進(jìn)行消息發(fā)送仓坞,執(zhí)行+load
方法。
這樣腰吟,+load
方法得以被調(diào)用无埃。
3. 總結(jié)
本篇文章主要是從_objc_init
開(kāi)始,結(jié)合map_images
毛雇、load_images
來(lái)探究類的加載過(guò)程嫉称。 如果你對(duì) dyld 還不是很熟悉,建議先看一看我的另一篇文章 iOS 應(yīng)用程序加載 灵疮,再來(lái)看這篇文章的時(shí)候會(huì)更清晰些织阅。
盡管類的加載細(xì)節(jié)是相同的,但加載的【時(shí)機(jī)】卻不盡相同震捣,為此有必要在這里也補(bǔ)充一下 懶加載類 與 非懶加載類荔棉。
補(bǔ)充:懶加載類與非懶加載類
非懶加載類:隨著程序啟動(dòng),在map_iamges
階段既完成加載的類.
懶加載類:當(dāng)?shù)谝淮问褂妙悤r(shí)才加載的類(通常為第一次發(fā)送消息時(shí))蒿赢。
我們可以通過(guò)實(shí)現(xiàn)+load
方法來(lái)迫使類提前加載润樱,即非懶加載類。
對(duì)于幾種實(shí)現(xiàn)+load方法的情況的堆棧情況如下圖:
- 主類實(shí)現(xiàn)
+load
當(dāng)主類實(shí)現(xiàn)+load
的時(shí)候诉植,在_read_images
的第⑨步中就會(huì)實(shí)現(xiàn)類祥国。
- 子類實(shí)現(xiàn)
+load
當(dāng)子類實(shí)現(xiàn)+load
時(shí),上圖堆棧中會(huì)執(zhí)行兩次realizeClassWithoutSwift
,如果前面探討的內(nèi)容你有仔細(xì)看的話舌稀,應(yīng)該記得啊犬,在realizeClassWithoutSwift
方法內(nèi),為了保證繼承鏈的完整性壁查,會(huì)遞歸調(diào)用realizeClassWithoutSwift
觉至,去實(shí)現(xiàn)父類。
- 分類實(shí)現(xiàn)
+load
分類實(shí)現(xiàn)+load
時(shí)睡腿,在prepare_load_methods
時(shí)语御,會(huì)先調(diào)用realizeClassWithoutSwift
,確保本類先實(shí)現(xiàn)席怪。所以堆棧信息是這樣的应闯。
- 均未實(shí)現(xiàn)
+load
當(dāng)上述+load
均未實(shí)現(xiàn)的時(shí)候,類的實(shí)現(xiàn)將直到第一次使用它時(shí)挂捻,這里的情況一般就是發(fā)送消息的時(shí)候了碉纺。在消息的慢速查找流程中,如果類還沒(méi)有實(shí)現(xiàn)刻撒,為了保證程序的正常運(yùn)行骨田,會(huì)在這里實(shí)現(xiàn)類,這也是符合正常邏輯的声怔。
才疏學(xué)淺态贤,文章如有誤導(dǎo)之處,還請(qǐng)指正醋火,若覺(jué)得對(duì)你有所幫助悠汽,也可以給我一個(gè)贊?? 感謝!