前言
我們?cè)?a href="http://www.itdecent.cn/p/b2a845340192" target="_blank">iOS類的加載原理(上)我們介紹了類的加載原理的大致流程,由篇幅過(guò)長(zhǎng),上篇文章未介紹完,這篇文章繼續(xù)展開(kāi)類的加載原理分析。
rwe,rw,ro的介紹
我們?cè)?a href="http://www.itdecent.cn/p/b2a845340192" target="_blank">iOS類的加載原理(上)簡(jiǎn)單介紹了,rwe,rw,ro,那么為什么會(huì)這些呢?
clean memory是指加載后不會(huì)發(fā)生更改的內(nèi)存,可以進(jìn)行移除從而節(jié)省更多的內(nèi)存空間
dirty memory是指在進(jìn)程運(yùn)行時(shí)會(huì)發(fā)生更改的內(nèi)存,類結(jié)構(gòu)一經(jīng)使用就會(huì)變 成dirty memory,因?yàn)檫\(yùn)行時(shí)會(huì)向它寫(xiě)入新的數(shù)據(jù),比如:創(chuàng)建一個(gè)新的方法緩存并從類中指向它,它比clean memory要昂貴得多,只要進(jìn)程在運(yùn)行,它就必須一直存在,,因?yàn)槿绻阈枰猚lean memory,系統(tǒng)可以從磁盤(pán)中重新加載,macOS可以選擇喚出dirty memory,因?yàn)閕OS不使用swap所以diry memory在iOS中代價(jià)很大,dirty memory是這個(gè)類數(shù)據(jù)被分成兩部分的原因,可以保持清的數(shù)據(jù)越多越好,可以保持清潔的數(shù)據(jù)越多越好,通過(guò)分離那些永遠(yuǎn)不會(huì)更改的數(shù)據(jù),可以把大部分的類數(shù)據(jù)存在clean memory
ro(class_ro_t)屬于clean memory,因?yàn)樗侵蛔x的,從磁盤(pán)加載進(jìn)來(lái)的(沙盒)
rw(class_rw_t)屬于dirty memory, 當(dāng)類第一次使用的時(shí)候運(yùn)行時(shí)會(huì)為類分配額外的存儲(chǔ)容量,這個(gè)運(yùn)行時(shí)分配的存儲(chǔ)容量是class_rw_t用于讀取-編寫(xiě)數(shù)據(jù),在這個(gè)數(shù)據(jù)結(jié)構(gòu)中,存儲(chǔ)了只在在運(yùn)于時(shí)才會(huì)生成的新信息,比如:所有的類都會(huì)鏈接成一個(gè)樹(shù)狀結(jié)構(gòu),通過(guò)First Subclass和Next Sibling Class指針實(shí)現(xiàn)的,這允許運(yùn)行時(shí)遍歷當(dāng)前使用的所有類,這對(duì)于使方法緩存無(wú)效非常有用,它的數(shù)據(jù)是從ro中復(fù)制過(guò)來(lái)的 drity memory是在手機(jī)端是非常昂貴的
rwe 當(dāng)category加載,添加新的方法時(shí),需要用到運(yùn)行時(shí),由ro只讀,我們無(wú)法更改,就需要rw,就會(huì)占用相當(dāng)多的內(nèi)存,不可能所有的類都會(huì)需要?jiǎng)討B(tài)修改,如果全部寫(xiě)到rw就會(huì)浪費(fèi)很多內(nèi)存,當(dāng)我們需要rwe的時(shí)候,就會(huì)根據(jù)一個(gè)ext的標(biāo)識(shí)創(chuàng)建,對(duì)rw做一些擴(kuò)展,優(yōu)化內(nèi)存,像我們的方法,協(xié)議,屬性會(huì)有變化的可能,所以存在rwe中,而類的父類信息,名字放在rw中。
指針強(qiáng)轉(zhuǎn)到數(shù)據(jù)結(jié)構(gòu)
在iOS類的加載原理(上)中我們介紹read_class中有cls->data()可以得到data的數(shù)據(jù),那么data的數(shù)據(jù)從哪來(lái),我們接著分析。
我們搜索realizeClassWithoutSwift找到相關(guān)實(shí)現(xiàn),我們找到下面兩行代碼:
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
我們運(yùn)行并斷點(diǎn)調(diào)試一下,如圖:

cls是一個(gè)地址,調(diào)用了data()就得到了ro這是為什么呢?我們接著往下看。
我們看下data這個(gè)函數(shù)的源碼
class_rw_t *data() const {
return bits.data();
}
是從bits里面拿到的data我們?cè)倏聪耣its,如下:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
這里bits& FAST_DATA_MASK這個(gè)值強(qiáng)轉(zhuǎn)成了指向class_rw_t類型的指針,結(jié)構(gòu)體指針可以取結(jié)構(gòu)中的數(shù)據(jù)。
attachCategories反推思路
我們上面介紹了rwe,rw,ro那么 rwe是在什么時(shí)候有賦值的呢,我們繼續(xù)分析。
在methodizeClass這個(gè)函數(shù)中auto rwe = rw->ext();由這行代碼控制rwe,經(jīng)過(guò)查找extAllocIfNeeded由這個(gè)函數(shù)控制,iOS類的加載原理(上)里面有介紹extAllocIfNeeded這個(gè)函數(shù)的賦值的點(diǎn)在attachCategories,而它又在attachToClass,load_categories_nolock有調(diào)用。
我們先在attachToClass打個(gè)斷點(diǎn),我們?cè)僬?strong>attachToClass的調(diào)用點(diǎn),結(jié)果發(fā)現(xiàn)是在methodizeClass調(diào)用的,我們先看下里面調(diào)用的代碼:
// 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);
attachToClass的調(diào)用是由previously控制的,我們發(fā)現(xiàn)previously是methodizeClass這個(gè)函數(shù)的形參,我們?cè)俅尾檎野l(fā)現(xiàn)是在realizeClassWithoutSwift這里調(diào)用了methodizeClass并傳入previously參數(shù),realizeClassWithoutSwift的previously也是由形參傳過(guò)來(lái)的,經(jīng)過(guò)一系的分析,它是一個(gè)備用參數(shù)(nil),這里不會(huì)調(diào)用,
我們?cè)倏聪聫垐D:

只會(huì)調(diào)用1559的attachToClass。
我們的推導(dǎo)過(guò)程順序如下:
realizeClassWithoutSwift-> methodizeClass-> attachToClass-> attachCategories
attachList算法
我們看下** attachCategories**的代碼,如下:
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
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();
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "LGPerson") == 0)
{
if (!isMeta) {
printf("LGPerson....\n");
}
}
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);
}
這里面調(diào)用的rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
我們看下attachLists這個(gè)函數(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;
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();
}
}
這是核心重點(diǎn),我們來(lái)分析下。
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
這個(gè)流程把a(bǔ)ddedLists[0]給了List,list是一維數(shù)組。
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();
}
這段代碼就是如果oldList為1,在lists的第addedCount元素插入數(shù)據(jù),接著再把a(bǔ)ddedLists元素從lists的0元素開(kāi)始插入。
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();
}
這段代碼倒序一個(gè)一個(gè)的插入到newArray中,最后會(huì)把newArray釋放掉 。
分類和主類加載的4種情況
我們添加一個(gè)RoPerson的分類RoPerson+RoA,并且寫(xiě)一個(gè)Load方法,驗(yàn)證是否是必須用Load方法才可以調(diào)用attachCategories。
同樣需要加入以下代碼,方便測(cè)試,如下:
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "RoPerson") == 0)
{
if (!isMeta) {
printf("RoPerson....\n");
}
}
然后我們?cè)?strong>attachCategories的上面測(cè)試代碼中打斷點(diǎn)調(diào)試,如圖:

我們的斷點(diǎn)進(jìn)入了attachCategories函數(shù)中。
1.RoPerson與其分類都有實(shí)現(xiàn)load方法(非懶加載)。
2.分類實(shí)現(xiàn)Load方法,主類不實(shí)現(xiàn)Load方法,經(jīng)過(guò)測(cè)試并沒(méi)有進(jìn)入attachCategories函數(shù)中。
3.分類沒(méi)有實(shí)現(xiàn)Load方法,主類有實(shí)現(xiàn)Load方法,經(jīng)過(guò)測(cè)試并沒(méi)有進(jìn)入attachCategories函數(shù)中。
4.分類沒(méi)有實(shí)現(xiàn)Load方法,主類也沒(méi)有實(shí)現(xiàn)Load方法,經(jīng)過(guò)測(cè)試并沒(méi)有進(jìn)入attachCategories函數(shù)中。
那么分類加載的流程是什么,我們接著分析。
分類加載流程跟蹤
我們先講上面的第一種情況RoPerson與其分類都有實(shí)現(xiàn)load方法。的流程。
我們?cè)?strong>_read_images這個(gè)函數(shù)加入的測(cè)試代碼中斷點(diǎn),如下圖:

然后斷點(diǎn)進(jìn)入realizeClassWithoutSwift函數(shù),在終端執(zhí)行以下命令,如圖:

當(dāng)我們?nèi)〉降?0的時(shí)候,報(bào)錯(cuò)了,也就是說(shuō)分類的方法,我們繼續(xù)往下執(zhí)行,走到了load_categories_nolock這個(gè)函數(shù)中。
我們看下當(dāng)前的堆棧,如圖:

接著我們?cè)诮K端在執(zhí)行以下命令,如圖:

在這是里打印出了我們的RoA這個(gè)分類,說(shuō)明加載了,也就是在運(yùn)行時(shí)的時(shí)候才加載。
這個(gè)時(shí)候,再繼續(xù)執(zhí)行,走到了attachCategories這個(gè)函數(shù)。
在以下代碼可以看出在處理方法,協(xié)議,屬性
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();
});
}
}
這里對(duì)方法又進(jìn)行了排序,mlists + ATTACH_BUFSIZ - mcount這里得到是二級(jí)指針,通過(guò)p mlists + ATTACH_BUFSIZ - mcount可以查看。
** rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);**這個(gè)函數(shù)把分類的方法加入到主類中。
多個(gè)分類加載
我們?cè)贋镽oPerson創(chuàng)建一個(gè)分類RoPerson+RoB,然后加入方法和屬性,重新運(yùn)行。
在進(jìn)入load_categories_nolock這個(gè)函數(shù)是,我們打印下count變量是2,如圖:

然后執(zhí)行以下命令,如圖:

這里是RoB的加載,我們過(guò)掉,繼續(xù)執(zhí)行,然后在load_categories_nolock中打印以下命令,如圖:

這里是RoA的加載,而這個(gè)時(shí)候在attachLists這個(gè)函數(shù)中走到了
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();
}
這段代碼,newArray也是二維存儲(chǔ),存的是分類以及主類方法的的指針。
五種情況ro-rw數(shù)據(jù)的加載
以上幾個(gè)是分類與主類都有l(wèi)oad的方法實(shí)現(xiàn)時(shí)的數(shù)據(jù)加載,那么其它的情況呢,我們接著分析。第一種情況以上已經(jīng)介紹。
2.我們先看分類實(shí)現(xiàn),主類沒(méi)有的情況,重新運(yùn)行工程。
如圖:

這里進(jìn)入了非懶加載的方式,也就是說(shuō)分類實(shí)現(xiàn)的load方法,導(dǎo)致主類進(jìn)入了非懶加載,那么主類的數(shù)據(jù)怎么來(lái)呢,我們斷點(diǎn)進(jìn)入realizeClassWithoutSwift這個(gè)函數(shù)查看,如圖:

這說(shuō)明主類與分類的數(shù)據(jù)已經(jīng)存在,伴隨著類的加載合到一起了,在data()獲取。
3.我們?cè)賮?lái)看分類沒(méi)有,主類有Load方法的實(shí)現(xiàn)情況,重新運(yùn)行工程,經(jīng)過(guò)分析,如圖:

這說(shuō)明主類實(shí)現(xiàn)了load方法,分類的方法也有了,同樣在data()獲取數(shù)據(jù)。
4.我們繼續(xù)看主類沒(méi)有實(shí)現(xiàn)load方法,分類也沒(méi)有實(shí)現(xiàn)Load方法的情況,重新運(yùn)行工程,先在main函數(shù)打個(gè)斷點(diǎn),如圖:

繼續(xù)執(zhí)行走到了realizeClassWithoutSwift這個(gè)函數(shù),是在第一次發(fā)送消息的時(shí)候加載的(懶加載),我們?cè)倏聪露褩?br>

是推遲到第一次發(fā)送消息的時(shí)候加載。
5.主類有Load方法,分類也有l(wèi)oad方法,但是有多個(gè)分類的情況(分類不全有),我們創(chuàng)建RoPerson+RoB這個(gè)分類,重新運(yùn)行工程,進(jìn)入的也是Realize non-lazy classes(非懶加載) (for +load methods and static instances)模式,也會(huì)走到load_categories_nolock這個(gè)函數(shù),這個(gè)函 數(shù)的count是2,也就是一個(gè)一個(gè)的加載,這里的count是在processCatlist(hi->catlist(&count));(閉包)得到的,其實(shí)也就是在_getObjc2CategoryList這個(gè)地方獲取的,由Macho決定的。
結(jié)語(yǔ)
這篇文章主要介紹了分類的加載,跟iOS類的加載原理(上)一起整體介紹了分類的加載原理,如有錯(cuò)誤,煩請(qǐng)大家留言指正,相互交流學(xué)習(xí)。