類的加載主要分如下幾個(gè)階段階段:1踩晶、類從鏡像文件映射到內(nèi)存中,并存儲(chǔ)到類表枕磁;2渡蜻、類結(jié)構(gòu)初始化,包括rw,ro等的setup茸苇;3排苍、加載類的properties、methedList学密、protocols淘衙、categorys。
那類是在程序運(yùn)行的哪個(gè)時(shí)機(jī)加載的腻暮?是如何加載的幔翰?類的相關(guān)方法、屬性西壮、協(xié)議、還有類別等都是什么時(shí)候加載的呢叫惊?
類的映射
類的映射時(shí)發(fā)生在程序啟動(dòng)過(guò)程中的款青。dyld啟動(dòng)工程中會(huì)調(diào)用libobjc庫(kù)的objc_init
初始化這個(gè)庫(kù),此時(shí)函數(shù)objc_init除了做一些初始工作之外還會(huì)向dyld注冊(cè)監(jiān)聽(tīng)函數(shù)霍狰,以便在鏡像文件映射抡草、取消映射和初始化objc時(shí)調(diào)用。這其中就有map_images函數(shù)蔗坯,進(jìn)行鏡像文件映射時(shí)會(huì)調(diào)用map_images函數(shù)進(jìn)行OC類的映射康震。通過(guò)源碼分析類的映射函數(shù)調(diào)用流程大概是這樣的:
objc_init
->map_images
-> map_images_nolock
-> _read_images
-> addNamedClass(cls, mangledName, replacing)
-> addClassTableEntry(cls)
。
這其中我們逐個(gè)進(jìn)行講解宾濒。
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();
tls_init();
static_init();// static_init() Run C++ static constructor functions. libc calls _objc_init() before dyld would call our static constructors, so we have to do it ourselves.
runtime_init(); // 類表allocatedClasses.init()和unattachedCategories.init(32)看似跟category相關(guān)的表
exception_init();//異常處理回調(diào)函數(shù)初始化
cache_init();
_imp_implementationWithBlock_init();
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
#if __OBJC2__
didCallDyldNotifyRegister = true;
#endif
}
源碼解讀:
environ_init()
讀取影響運(yùn)行時(shí)的環(huán)境變量腿短。如果需要,還可以打印環(huán)境變量幫助。
tls_init()
關(guān)于線程key的綁定 - 比如每條線程數(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(), 因此我們必須自己調(diào)用。
runtime_init()
類表allocatedClasses.init()和unattachedCategories.init(32)看似跟category相關(guān)的表隘截,這個(gè)在后面類的
imp_implementationWithBlock_init(void)
啟動(dòng)回調(diào)機(jī)制。通常不會(huì)這么做。因?yàn)樗械某跏荚挾际嵌栊缘哪模菍?duì)于某些進(jìn)程,我們會(huì)迫不及待地加載trampolines dylib疹鳄。
_dyld_objc_notify_register
向dyld注冊(cè)監(jiān)聽(tīng)函數(shù)拧略。以便在鏡像文件映射、取消映射和初始化objc時(shí)調(diào)用尚辑。Dyld將使用包含objc-image-info的鏡像文件的數(shù)組回調(diào)給map_images函數(shù)辑鲤。
runtime_init()
運(yùn)行時(shí)環(huán)境初始化,里面主要是類表allocatedClasses 和unattachedCategories
exception_init()
初始化ibobjc的異常處理系統(tǒng)
map_images源碼解析
函數(shù)map_images是在dyld進(jìn)行鏡像文件映射時(shí)調(diào)用的回調(diào)函數(shù)杠茬,dyld將使用包含objc-image-info的鏡像文件的數(shù)組回調(diào)給map_images函數(shù)月褥,然后調(diào)用map_images_nolock完成接下來(lái)的事情:
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_nolock源碼解析
主要完成如下幾件事情:
- 讀取鏡像文件
通過(guò)函數(shù)addHeader讀取鏡像文件(mach-o)獲取文件頭部信息列表弛随,獲取含有Objective-C元數(shù)據(jù)(metadata)的鏡像文件列表hList和個(gè)數(shù)hCount,同時(shí)統(tǒng)計(jì)類的個(gè)數(shù)totalClasses宁赤。
const headerType *mhdr = (const headerType *)mhdrs[i];
auto hi = addHeader(mhdr, mhPaths[i], totalClasses, unoptimizedTotalClasses);
if (!hi) {
// no objc data in this entry
continue;
}
hList[hCount++] = hi;
- 判斷是否第一次調(diào)用舀透,做一些初始化工作
// Perform one-time runtime initialization that must be deferred until
// the executable itself is found. This needs to be done before
// further initialization.
// (The executable may not be present in this infoList if the
// executable does not contain Objective-C code but Objective-C
// is dynamically loaded later.
if (firstTime) {
sel_init(selrefCount);
arr_init();
.......
}
- 將第一步獲取的hCount、hList[]和totalClasses傳遞給_read_images完成類的映射决左。
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}
read_images源碼解析
read_images是加載類信息的主要函數(shù)愕够,看其函數(shù)聲明:
/***********************************************************************
* _read_images
* Perform initial processing of the headers in the linked
* list beginning with headerList.
*
* Called by: map_images_nolock
*
* Locking: runtimeLock acquired by map_images
**********************************************************************/
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
由于實(shí)現(xiàn)代碼比較多,這里根據(jù)其注釋整理了它的主要做的功能:
1佛猛、條件控制進(jìn)行一次的加載惑芭,如果是第一次加載會(huì)創(chuàng)建一個(gè)類名映射表
gdb_objc_realized_classes
;
2继找、Fix up @selector references.修復(fù)預(yù)編譯階段的@selector
的混亂問(wèn)題遂跟,比如地址偏移不一致等;
3婴渡、Discover classes. Fix up unresolved future classes. Mark bundle classes. 處理錯(cuò)誤混亂的類幻锁;
4、讀取類表边臼,調(diào)用readClass
添加類名映射哄尔;
5、Fix up remapped classes.修復(fù)重映射一些沒(méi)有被鏡像文件加載進(jìn)來(lái)的類柠并;
6岭接、 Fix up old objc_msgSend_fixup call sites.修復(fù)一些消息;
7堂鲤、Discover protocols. Fix up protocol refs.當(dāng)我們類里面有的時(shí)候:readProtocol亿傅;
8、Fix up @protocol references.修復(fù)沒(méi)有被加載的協(xié)議瘟栖;
9葵擎、Discover categories. Only do this after the initial category attachment has been done. 分類處理;
10半哟、Realize non-lazy classes (for +load methods and static instances).懶加載類的加載處理酬滤;
11、Realize newly-resolved future classes, in case CF manipulates them.沒(méi)有被處理的類寓涨,優(yōu)化那些被非法操作的類盯串。
這其中這里要講的是1、3戒良、6体捏、8和9。其中6、8和9會(huì)在類的初始化過(guò)過(guò)程中分析几缭。
- gdb_objc_realized_classes的創(chuàng)建
在程序第一次進(jìn)來(lái)的時(shí)候會(huì)根據(jù)傳入的類的總數(shù)totalClasses創(chuàng)建一個(gè)表gdb_objc_realized_classes
河泳,gdb_objc_realized_classes
將存儲(chǔ)所有類和類名的映射,不管這個(gè)類是否實(shí)現(xiàn)年栓。
// namedClasses
// Preoptimized classes don't go in this table.
// 4/3 is NXMapTable's load factor
int namedClassesSize =
(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
gdb_objc_realized_classes =
NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
- 讀取類表_getObjc2ClassList
第4步中中拆挥,根據(jù)傳入的header_info **hList 通過(guò)_getObjc2ClassList
遍歷讀取出OC類列表,然后把一個(gè)個(gè)Class交給readClass
函數(shù)來(lái)處理:
for (EACH_HEADER) {
if (! mustReadClasses(hi, hasDyldRoots)) {
// Image is sufficiently optimized that we need not call readClass()
continue;
}
classref_t const *classlist = _getObjc2ClassList(hi, &count);
bool headerIsBundle = hi->isBundle();
bool headerIsPreoptimized = hi->hasPreoptimizedClasses();
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源碼解析
這一步主要讀取類名某抓,并通過(guò)函數(shù)addNamedClass將類名和類進(jìn)行映射纸兔,寫(xiě)入,然后調(diào)用addClassTableEntry把類添加到類表里頭否副。
addNamedClass(cls, mangledName, replacing);
} else {
Class meta = cls->ISA();
const class_ro_t *metaRO = meta->bits.safe_ro();
ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
}
addClassTableEntry(cls);
- addNamedClass源碼解析
函數(shù)addNamedClass中實(shí)際上就是把類和類名通過(guò)NXMapInsert方法寫(xiě)入到我們之前提到的第一次進(jìn)來(lái)時(shí)創(chuàng)建的類名映射表gdb_objc_realized_classes
里面:
/***********************************************************************
* addNamedClass
* Adds name => cls to the named non-meta class map.
* Warns about duplicate class names and keeps the old mapping.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
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));
// wrong: constructed classes are already realized when they get here
// ASSERT(!cls->isRealized());
}
- addClassTableEntry源碼解析
函數(shù)addClassTableEntry實(shí)際上是將類注冊(cè)到所有類的類表allocatedClasses
中汉矿。這個(gè)表不止有類,還包含元類备禀。這個(gè)表的初始化是在前面提到的_objc_init
中調(diào)用runtime_init
完成的负甸。
/***********************************************************************
* addClassTableEntry
* Add a class to the table of all classes. If addMeta is true,
* automatically adds the metaclass of the class as well.
* Locking: runtimeLock must be held by the caller.
**********************************************************************/
static void
addClassTableEntry(Class cls, bool addMeta = true)
{
runtimeLock.assertLocked();
// This class is allowed to be a known class via the shared cache or via
// data segments, but it is not allowed to be in the dynamic table already.
auto &set = objc::allocatedClasses.get();
ASSERT(set.find(cls) == set.end());
if (!isKnownClass(cls))
set.insert(cls);
if (addMeta)
addClassTableEntry(cls->ISA(), false);
}
至此加載結(jié)類就都緩存到了類表里面了。類存儲(chǔ)到類表之后就開(kāi)始進(jìn)行類的初始化痹届。
類的初始化
類的初始化是在函數(shù)realizeClassWithoutSwift中實(shí)現(xiàn)的。在_readImages完成類的映射之后打月,接著就調(diào)用了realizeClassWithoutSwift進(jìn)行初始化:
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist = hi->nlclslist(&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);
}
}
在這段代碼中我們發(fā)現(xiàn)它是通過(guò)讀取nlclslist(&count)表來(lái)獲取classlist的
classref_t const *classlist = hi->nlclslist(&count);
這與我們之前讀取_getObjc2ClassList表不一樣队腐。這又是為什么呢?通過(guò)上面的注釋“Realize non-lazy classes (for +load methods and static instances)”我們發(fā)現(xiàn)原來(lái)這里面實(shí)現(xiàn)的是non-lazy classes奏篙,翻譯過(guò)來(lái)就是懶加載類柴淘。那什么是懶加載類?注釋里也說(shuō)了秘通,就是實(shí)現(xiàn)了+load方法或者靜態(tài)實(shí)例變量为严。這里我們就明白了,這里的nlclslist其實(shí)讀取的是懶加載類的表肺稀。有了懶加載類就有非懶加載類第股,那根據(jù)注釋的意思,以及我們對(duì)realizeClassWithoutSwift的調(diào)用流程的分析话原,我們對(duì)懶加載類和非懶加載類做了如下總結(jié):
1夕吻、為什么要分為懶加載類和非懶加載類?
前面已經(jīng)完成了類的映射和注冊(cè)繁仁,接下來(lái)是類的初始化了涉馅。類的初始化實(shí)際上就是生成類對(duì)象,并設(shè)置類結(jié)構(gòu)的各個(gè)屬性黄虱。那這里就有一個(gè)問(wèn)題稚矿,初始化類對(duì)象勢(shì)必開(kāi)辟一定的內(nèi)存空間,在一個(gè)比較大的項(xiàng)目中,如果所有類都在啟動(dòng)時(shí)初始化晤揣,那一定會(huì)占用不少內(nèi)存空間桥爽,而且在程序運(yùn)行的某一個(gè)時(shí)間內(nèi)特別是啟動(dòng)階段,我們并不一定需要訪問(wèn)所有的類碉渡,因此如果在啟動(dòng)階段就初始話所有的類那必將造成性能損耗聚谁,同時(shí)也浪費(fèi)大量?jī)?nèi)存。因此滞诺,類的初始話時(shí)機(jī)非常重要形导。那類都在什么時(shí)候初始話呢?當(dāng)然是在需要的時(shí)候初始化比較好习霹。因此朵耕,根據(jù)類的初始化時(shí)機(jī),我們把類分成懶加載類和非懶加載類淋叶。非懶加載類是在程序啟動(dòng)的時(shí)候初始化阎曹,也就是在map_images時(shí)期初始化;懶加載類是在程序運(yùn)行之后根據(jù)需要初始化的煞檩,一般來(lái)說(shuō)是在第一次訪問(wèn)類的時(shí)候會(huì)判斷類有沒(méi)有實(shí)現(xiàn)处嫌,沒(méi)有的話再進(jìn)行初始化。
2斟湃、懶加載類和非懶加載類分別的初始化流程是什么樣的呢熏迹?
非懶加載類:前面我們知道,非懶加載類是在app啟動(dòng)時(shí)完成初始化的凝赛。而且前面頁(yè)說(shuō)到_dyld_objc_notify_register
像dyld注冊(cè)了三個(gè)函數(shù)注暗,其中就有,map_images在鏡像文件映射時(shí)調(diào)用墓猎,_load_images則是初始化完成之后并調(diào)用類和category的+load方法捆昏,要調(diào)用類或者category的+load方法,那類或者category所屬的類就得在這之前被初始化毙沾,因此非懶加載類會(huì)在map_images中,在_load_images函數(shù)之前被初始化左胞。其函數(shù)流程如下:
map_images
->map_images_nolock
->_read_images
->readClass
->realizeClassWithoutSwift
懶加載類:未實(shí)現(xiàn)+load方法膨俐,在第一次向類發(fā)送消息時(shí)被動(dòng)調(diào)用realizeClassWithoutSwift以實(shí)現(xiàn)類的初始化。其調(diào)用流程如下:
lookUpImpOrForward
->realizeAndInitializeIfNeeded_locked
->realizeClassMaybeSwiftAndLeaveLocked
->realizeClassMaybeSwiftMaybeRelock
->realizeClassWithoutSwift
消息發(fā)送流程中罩句,只有相應(yīng)的類初始化才可能有值焚刺,才可能存在方法列表。所以消息發(fā)送時(shí)要先檢查類是否已經(jīng)被初始化了门烂。
realizeClassWithoutSwift源碼解析
雖然類的初始化時(shí)機(jī)不一樣乳愉,但是不管在哪個(gè)時(shí)機(jī)兄淫,初始化過(guò)程都是通過(guò)調(diào)用函數(shù)realizeClassWithoutSwift進(jìn)行實(shí)現(xiàn)的:
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
關(guān)于它初始話的內(nèi)容,我們必須得先了解類的底層結(jié)構(gòu)蔓姚。根據(jù)這個(gè)函數(shù)的注釋捕虽,它是第一次對(duì)類執(zhí)行初始化,不包括swift相關(guān)的初始化坡脐。接下來(lái)通過(guò)源碼分析realizeClassWithoutSwift的主要功能:
1泄私、創(chuàng)建并設(shè)置class_rw_t;
2备闲、初始化緩存晌端;
3、類和元類的實(shí)現(xiàn)恬砂;
4咧纠、設(shè)置nonpointer標(biāo)記;
5泻骤、設(shè)置父類和元類漆羔;
6、設(shè)置C++構(gòu)造和析構(gòu)函數(shù).cxx_construct/destruct
7狱掂、如果有父類則添加到父類的子類表里演痒;如果沒(méi)有父類,則設(shè)置為根類rootClass;
8趋惨、調(diào)用methodizeClass完成方法嫡霞、屬性、協(xié)議等的加載希柿。
接下來(lái)主要介紹1、3和8养筒。其他源碼比較簡(jiǎn)單曾撤,也有相應(yīng)的注釋。
copy class_ro_t 到 class_rw_t
class_rw_t *rw;
auto ro = (const class_ro_t *)cls->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);
}
因?yàn)榇蟛糠值念愒畏啵ㄎ覀冏远x的類都不是 future class挤悉,所以這里只分析else部分的代碼。當(dāng)我們把類加載到內(nèi)存中時(shí)巫湘,會(huì)創(chuàng)建一個(gè)Class装悲,同時(shí)會(huì)將編譯時(shí)確定的類相關(guān)的數(shù)據(jù)比如說(shuō)成員變量(ivars)、屬性(properties)尚氛、方法(methodList)诀诊、協(xié)議(protocols)等數(shù)據(jù)都存在class_ro_t結(jié)構(gòu)當(dāng)中,并在把class_ro_t的指針objc_class存儲(chǔ)到類的bits屬性中阅嘶。因此我們可以看到代碼中通過(guò)cls->data()把原始的class_ro_t(ro)讀取出來(lái)属瓣,然后創(chuàng)建一個(gè)class_rw_t (rw)载迄,然后把ro復(fù)制到rw中,最后把rw關(guān)聯(lián)到class里面抡蛙,同時(shí)設(shè)置初始化狀態(tài)flag护昧。這其中class_ro_t、class_rw_t和Class的關(guān)系都可以在類的底層結(jié)構(gòu)里面看到粗截。
父類惋耙、元類的初始化
函數(shù)realizeClassWithoutSwift中不止當(dāng)前類初始化,還對(duì)父類熊昌、元類遞歸初始化绽榛,確立好類的繼承鏈和isa指向:
// 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.
// This assumes that none of those classes have Swift contents,
// or that Swift's initializers have already been called.
// fixme that assumption will be wrong if we add support
// for ObjC subclasses of Swift classes.
supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
methodizeClass源碼解析
最后realizeClassWithoutSwift會(huì)調(diào)用methodizeClass函數(shù)完成methods、properties浴捆、protocols和category等的加載蒜田。接下來(lái)源碼分析:
/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
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), nullptr);
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);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
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 {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
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
}
加載方法列表(methods)
在類的初始化過(guò)程中加載方法列表相對(duì)重要,也相對(duì)復(fù)雜选泻。首先它會(huì)從ro中讀取編譯時(shí)確定的方法列表baseMethodList:
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
讀取baseMethods之后會(huì)調(diào)用prepareMethodLists冲粤,然后baseMethods作為一個(gè)元素加入到數(shù)組addedLists中。這里也可以看出方法列表底層其實(shí)是個(gè)二維數(shù)組页眯。
// Add method lists to array.
// Reallocate un-fixed method lists.
// The new methods are PREPENDED to the method list array.
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*/);
}
}
然后通過(guò)fixupMethodList對(duì)方法列表進(jìn)行排序:
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
runtimeLock.assertLocked();
ASSERT(!mlist->isFixedUp());
// fixme lock less in attachMethodLists ?
// dyld3 may have already uniqued, but not sorted, the list
if (!mlist->isUniqued()) {
mutex_locker_t lock(selLock);
// Unique selectors in list.
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name());
meth.setName(sel_registerNameNoLock(name, bundleCopy));
}
}
// Sort by selector address.
// Don't try to sort small lists, as they're immutable.
// Don't try to sort big lists of nonstandard size, as stable_sort
// won't copy the entries properly.
if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
method_t::SortBySELAddress sorter;
std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
}
// Mark method list as uniqued and sorted.
// Can't mark small lists, since they're immutable.
if (!mlist->isSmallList()) {
mlist->setFixedUp();
}
}
可以看出方法排序是按SEL地址大小進(jìn)行排序的梯捕,這也是為什么我們?cè)?a href="http://www.reibang.com/p/b75ce9c94ee3" target="_blank">方法查找的時(shí)候遍歷方法列表時(shí)采用二分法查找的原因。
加載屬性列表(baseProperties)和協(xié)議列表(baseProtocols)
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);
}
這其中我看到一個(gè)出鏡率很高的變量rwe窝撵,rwe的實(shí)際結(jié)構(gòu)是class_rw_ext_t傀顾,這個(gè)rwe是在需要運(yùn)行時(shí)修改類結(jié)構(gòu)時(shí)才會(huì)被創(chuàng)建的。在_map_images基本不會(huì)創(chuàng)建(點(diǎn)擊了解class_rw_ext_t可多的信息)碌奉。而rwe是有可能在load_images階段創(chuàng)建短曾,下面會(huì)有介紹。
加載類別(categories)
說(shuō)到類Category加載赐劣,相對(duì)來(lái)說(shuō)比較復(fù)雜嫉拐。首先Category是類的擴(kuò)展,所以它加載必然跟類有關(guān)魁兼,接下來(lái)我們先看Category的底層結(jié)構(gòu)category_t:
struct category_t {
const char *name;
classref_t cls;
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
WrappedPtr<method_list_t, PtrauthStrip> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
可以看到Category不僅包含Class指針婉徘,還有很多信息,包括方法列表咐汞、屬性列表等盖呼。這些方法在加載的時(shí)候都是要合并到類的方法列表里面去的。那Category是什么時(shí)候加載的呢化撕?其實(shí)Category的也跟Class一樣几晤,有懶加載和非懶加載。Category的懶加載和非懶加載根類的懶加載和非懶加載有一定的關(guān)系植阴,經(jīng)過(guò)源碼運(yùn)行反復(fù)調(diào)試锌仅,總結(jié)了他們之間如下關(guān)系:
1章钾、當(dāng)類和它的Category都實(shí)現(xiàn)+load方法,類和category(如果有多個(gè)category热芹,只要有一個(gè)category實(shí)現(xiàn)+load贱傀,其他category是非懶加載),是非懶加載的伊脓。
2府寒、當(dāng)類實(shí)現(xiàn)+load,Category沒(méi)有實(shí)現(xiàn)+load报腔, 是非懶加載的株搔。
3、當(dāng)類沒(méi)有實(shí)現(xiàn)+load纯蛾,Category實(shí)現(xiàn)+load纤房,也是非懶加載的。
4翻诉、當(dāng)類和它的Category都沒(méi)有實(shí)現(xiàn)+load方法炮姨,是懶加載的。
那么這四種情況的category什么時(shí)候被加載的呢碰煌?我們?cè)谇懊嫣岬竭^(guò)舒岸,_objc_init會(huì)像dlyd注冊(cè)幾個(gè)函數(shù),其中就有一個(gè)load_images芦圾,這個(gè)函數(shù)是在鏡像文件初始化完成之后調(diào)用的蛾派。第1種情況是類在類初始化階段(_map_images)被加載,它的Category會(huì)在load_images階段被加載个少。而另外2洪乍、3種情況的Category會(huì)在_map_images階段類加載被讀取到class_ro_t中。而第4種情況則會(huì)在類第一次被訪問(wèn)的時(shí)候跟類一起被加載夜焦。接下來(lái)我們通過(guò)源碼調(diào)試驗(yàn)證一下壳澳。
1、當(dāng)類和它的Category都實(shí)現(xiàn)+load方法
- 調(diào)試demo
首先我們創(chuàng)建一個(gè)demo在源碼調(diào)試糊探,在demo中創(chuàng)建一個(gè)MyObject和它的一個(gè)category(addition),demo如下:
然后運(yùn)行源碼工程河闰。在_map_images流程的methodizeClass函數(shù)中有如下一段代碼:
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
正常情況下科平,本來(lái)期待的是通調(diào)用attachToClass到attachCategories,在attachCategories里面實(shí)現(xiàn)把Category加載到類結(jié)構(gòu)中姜性。所以我們?cè)赼ttachCategories函數(shù)中專門(mén)為MyObject類打了個(gè)斷點(diǎn)瞪慧。通過(guò)斷點(diǎn)調(diào)試發(fā)現(xiàn)這個(gè)流程根本不是從_map_images走到attachCategories,而真正調(diào)用attachCategories時(shí)在load_images這個(gè)階段部念,一下是源碼調(diào)試階段:
這樣Category在啟動(dòng)流程的加載時(shí)機(jī)我們就找到了弃酌。其流程就是
load_images
->loadAllCategories
->load_categories_nolock
->attachCategories
氨菇。
這一流程只是針對(duì)類和分類實(shí)現(xiàn)了+load方法的情況,當(dāng)然在這個(gè)流程之前Category的類作為非懶加載類已經(jīng)在_map_images階段實(shí)現(xiàn)了加載妓湘。類加載完成之后在加載分類查蓉。加載分類并關(guān)聯(lián)到類是在函數(shù)attachCategories中實(shí)現(xiàn)的。
- attachCategories源碼解析
接下來(lái)在attachCategories源碼查看Category加載的流程:
// 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, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
const char *mangledName = cls->mangledName();
if (strcmp("MyObject", mangledName) == 0) {
if(!cls->isMetaClass()){//避免元類的干擾
printf("我來(lái)了 MyObject");//MyObject是一個(gè)實(shí)現(xiàn)了+load方法的類榜贴,他還有一個(gè)category名字叫addtion也實(shí)現(xiàn)了+load方法豌研,
}
}
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
/*
* Only a few classes have more than 64 categories during launch.
* This uses a little stack, and avoids malloc.
*
* Categories must be added in the proper order, which is back
* to front. To do that with the chunking, we iterate cats_list
* from front to back, build up the local buffers backwards,
* and call attachLists on the chunks. attachLists prepends the
* lists, so the final result is in the expected order.
*/
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
auto rwe = cls->data()->extAllocIfNeeded();
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
首先會(huì)先創(chuàng)建一個(gè)class_rw_ext_t結(jié)構(gòu)變量rwe:
auto rwe = cls->data()->extAllocIfNeeded();
rwe前面有提到過(guò)。然后遍歷Category列表唬党,因?yàn)榭赡苁嵌鄠€(gè)鹃共,然后把每個(gè)category的方法列表、屬性列表驶拱、協(xié)議類表等都分別添加到rwe對(duì)應(yīng)的列表中霜浴。這里依然以category的方法列表為例,來(lái)分析這個(gè)過(guò)程:
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[i];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
......
}
首先通過(guò)函數(shù)methodsForMeta根據(jù)是否是元類獲取實(shí)例方法或者類方法列表蓝纲。然后通過(guò)prepareMethodLists先對(duì)方法進(jìn)行一波排序阴孟。接著調(diào)用attachLists函數(shù),把方法列表添加到rwe的methods中驻龟。接下來(lái)看attachLists是如何實(shí)現(xiàn)的:
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;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--)
newArray->lists[i + addedCount] = array()->lists[i];
for (unsigned i = 0; i < addedCount; i++)
newArray->lists[i] = addedLists[i];
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<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;
for (unsigned i = 0; i < addedCount; i++)
array()->lists[i] = addedLists[i];
validate();
}
}
這里我們可以看到methods是個(gè)二維數(shù)組温眉。而且新增的category方法列表總是被放在最前面。當(dāng)有新列表進(jìn)來(lái)時(shí)翁狐,如果沒(méi)有舊的方法列表則直接賦值类溢,如果有舊的方法列表,會(huì)重新計(jì)算數(shù)組大小重新創(chuàng)建一個(gè)新數(shù)組露懒,然后把舊的數(shù)據(jù)移到后面闯冷,新的加到前面。這樣新的方法總是能夠在最前面懈词。這也是為什么方法查找時(shí)和類的方法同名時(shí)總是先調(diào)用category方法的原因蛇耀。
2、當(dāng)類和它的Category其中之一實(shí)現(xiàn)+load方法
這時(shí)候類是非懶加載類坎弯,會(huì)在啟動(dòng)期間被加載纺涤。然后category在類初始化階段被加載到class_ro_t中。接下來(lái)源碼調(diào)試抠忘。首先創(chuàng)建一個(gè)類MyObject撩炊,并且創(chuàng)建兩個(gè)Category為CatA和CatB,然后分別實(shí)現(xiàn)兩個(gè)方法崎脉,然后我們針對(duì)當(dāng)前類的初始化的流程上打斷點(diǎn)拧咳,觀察方法列表,證明方法列表加載到class_ro_t中囚灼。demo如下:
接下來(lái)運(yùn)行程序祭衩,定位到MyObject讀取方法列表的代碼:
根據(jù)調(diào)試結(jié)果證明這兩種情況的類確實(shí)是非懶加載類,啟動(dòng)時(shí)會(huì)加載阅签,同時(shí)也會(huì)加載其對(duì)應(yīng)的category掐暮。
3、當(dāng)類和它的Category都沒(méi)實(shí)現(xiàn)+load方法
在剛才的demo上去掉+load方法愉择,這樣類就不會(huì)在啟動(dòng)階段初始化了劫乱。接著我們?cè)趩?dòng)之后去調(diào)用類的方法:
總結(jié)
類加載過(guò)程比較復(fù)雜。類加載又分為懶加載和非懶加載锥涕。跟+load方法有關(guān)衷戈。實(shí)現(xiàn)不管是分類和本類實(shí)現(xiàn)+load方法,他都是非懶加載類层坠。會(huì)在啟動(dòng)階段被初始化殖妇,而非懶加載類則會(huì)在第一次被訪問(wèn)的時(shí)候調(diào)用。因此在這里面+load方法在使用上的特別注意破花,不能用的太多谦趣,會(huì)影響啟動(dòng)速度(點(diǎn)擊了解app啟動(dòng)優(yōu)化)。類的加載邏輯跟類的底層結(jié)構(gòu)密切相關(guān)座每,想了解加載過(guò)程首先得了解類的底層結(jié)構(gòu)(點(diǎn)擊了解類的底層結(jié)構(gòu))和APP的啟動(dòng)流程(點(diǎn)擊了解APP的啟動(dòng)流程)前鹅;
備注:本文調(diào)試的源碼是objc4-818.2版本的;