引入
在 OC底層原理18-分類的加載 中,我們探究了分類的加載時機(jī),得出分類和類在是否實(shí)現(xiàn)load方法,即是否是懶加載類/分類,類中的方法加載時機(jī)不同,這篇我們再來研究一下類和分類中load方法搭配使用的時候,方法的加載時機(jī),屬性、協(xié)議、變量做一個總結(jié),不做板書,有興趣可以自己研究。
類和分類搭配加載
| 搭配情況 | 類 | 分類 |
|---|---|---|
| 情況1 | 非懶加載類(實(shí)現(xiàn)load) | 非懶加載分類(實(shí)現(xiàn)load) |
| 情況2 | 非懶加載類(實(shí)現(xiàn)load) | 懶加載分類(不實(shí)現(xiàn)load) |
| 情況3 | 懶加載類(不實(shí)現(xiàn)load) | 非懶加載分類(實(shí)現(xiàn)load) |
| 情況4 | 懶加載類(不實(shí)現(xiàn)load) | 懶加載分類(不實(shí)現(xiàn)load) |
一、非懶加載類和非懶加載分類
主類實(shí)現(xiàn)load方法,分類實(shí)現(xiàn)load方法
.main中不對類做任何初始化,依然用分類加載中的工程,運(yùn)行打印輸出流程

- 從打印流程可以看出,
類和分類中的方法在Main函數(shù)之前就完成了加載,即編譯期就完成了加載。
在methodizeClass方法中,我們找到了從ro中取出方法,存到類中

在這里打斷點(diǎn),往下走一步,打印
list,驗(yàn)證方法是否從ro取出來了
- 發(fā)現(xiàn)第一次進(jìn)入
methodizeClass方法,只取了主類GomoPerson里面的方法,然后調(diào)用prepareMethodLists,對方法根據(jù)sel的地址進(jìn)行排序,然后存入類中,在prepareMethodLists中已實(shí)現(xiàn),可自行查看。
接著又調(diào)用了attachCategories添加分類,會先取出類中的方法調(diào)用attachLists進(jìn)行處理,然后又會對觸發(fā)attachCategories的分類GomuPerons+GomuB中的方法attachLists進(jìn)行處理
接著GomuPerons+GomuA再次觸發(fā)attachCategories進(jìn)行attachLists處理
attachLists的源碼如下
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
//:-- 第三次添加GomuPerson+GomuA的方法/屬性/協(xié)議,
//:-- 此時list是一個二維數(shù)組,
//:-- 經(jīng)過此處,GomuPerson+GomuA的方法列表被加到了list[0],
//:-- GomuPerson+GomuB的方法列表被移到了list[1],
//:-- GomuPerson的方法列表被移到了list[2]
// 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) {
//:-- 第一次添加GomuPerson的方法/屬性/協(xié)議,
//:-- 此時list是一個一維數(shù)組
// 0 lists -> 1 list
list = addedLists[0];
}
else {
//: -- 第二次添加GomuPerson+GomuB的方法/屬性/協(xié)議,
//:-- 此時list是一個二維數(shù)組,
//:-- 經(jīng)過此處,GomuPerson+GomuB的方法被加到了list[0],
//:-- GomuPerson的方法被移到了list[1]
// 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]));
}
}
-
分類方法也會被prepareMethodLists排序 - 經(jīng)過
attachLists處理,最后加載的分類,處于list列表第一個位置,這就是為什么分類和類中相同方法,會調(diào)用分類中的方法,多個分類則會調(diào)用最后編譯的分類中的方法 -
多個分類相同方法的調(diào)用取決于該分類被編譯的先后,如下圖
image.png - 這里文件順序的越上面表示越先被編譯。
- 該圖這樣的順序則會優(yōu)先調(diào)用
GomuPerson+GomuA中的同名方法 - 如果把
GomuPerson+GomuB拖到下面,則會優(yōu)先調(diào)用GomuPerson+GomuB中的同名方法
總結(jié) :
- 非懶加載類和非懶加載分類情況下,方法的加載會提前到編譯期完成,在load_image的時候加載完成。
- 如果有類的分類,類中的list會變成一個二維或多維數(shù)組。越后編譯的分類越插入到
list最前面,這也是分類同名方法哪個優(yōu)先調(diào)用的原因。
二、非懶加載類和懶加載分類
主類實(shí)現(xiàn)load方法,分類不實(shí)現(xiàn)load方法
打印流程

- 發(fā)現(xiàn)只是進(jìn)行了
類的加載,沒有動態(tài)添加分類
在methodizeClass中下如下斷點(diǎn)

打印
list
- 發(fā)現(xiàn)從
data()讀取出來的數(shù)據(jù)中,就已經(jīng)加載好了類和分類中的所有方法
過一下斷點(diǎn),等prepareMethodLists執(zhí)行之后再打印list

- 發(fā)現(xiàn)
prepareMethodLists方法對list進(jìn)行了排序,把同名方法按順序排在前面,其他方法依舊按照sel地址進(jìn)行排序
總結(jié):
非懶加載類和懶加載分類的情況下,方法的加載也在編譯器完成,不同的是在data()中就已經(jīng)完成,methodizeClass中只是進(jìn)行重新排序。
三、懶加載類和非懶加載分類
主類不實(shí)現(xiàn)load方法,分類實(shí)現(xiàn)load方法
打印流程

- 發(fā)現(xiàn)調(diào)用了
methodizeClass,這個只有在主類中有load的情況才會調(diào)用,但是現(xiàn)在主類中沒有實(shí)現(xiàn)load依然就調(diào)用了,說明非懶加載分類會迫使懶加載類以非懶加載類的形式來提前加載數(shù)據(jù)
在methodizeClass中下如下斷點(diǎn)

打印
list
- 發(fā)現(xiàn)這種情況下,在
methodizeClass中打印data(),只有主類的方法
等經(jīng)過attachCategories方法后,在attachLists中打下個斷點(diǎn),查看方法何時存到類中

- 第一次還是和第一種情況
非懶加載類和非懶加載分類步驟類似,把主類的方法先加入list,自行打印list查看 - 不同的是,這種情況下,
GomuPerson+GomuA和GomuPerson+GomuB中的方法被一起加到了list中,如下圖打印
image.png
在attachLists最后一個條件中,即1維變2維的方法中,只是進(jìn)行了memcpy內(nèi)存拷貝,把GomuPerson移到了list的最后面,然后把addedLists加到了list前面,addedLists[0]中存的是GomuPerson+GomuB,所以調(diào)用gomu_instanceMethod1方法,會執(zhí)行分類GomuB中gomu_instanceMethod1的方法
總結(jié):
懶加載類和非懶加載分類情況下,編譯期會迫使主類加載,這種情況下也是在編譯期就完成了類和分類的加載,但是在load_image之前
四、懶加載類和懶加載分類
主類不實(shí)現(xiàn)load方法,分類不實(shí)現(xiàn)load方法
打印流程

-
懶加載類和懶加載分類的情況下,類和分類都不會提前在編譯期加載。
在main函數(shù)中調(diào)用一次方法,即objc_msgSend一次,查看打印

-
類和分類的加載被推遲到了main函數(shù)之后,之后調(diào)用順序和第二種情況非懶加載類和懶加載分類一模一樣,也是在data()中就完成了類和分類的方法加載
總結(jié):
懶加載類和懶加載分類的情況下,類和分類的加載推遲到了消息第一次調(diào)用,在data()中就完成了`方法的加載
二、推薦使用
由上面分析可得出,我們的類中應(yīng)該減少load方法的使用,不僅可以節(jié)約資源,還能減少編譯時間,切記切記,慎用load方法
三、屬性、協(xié)議、變量的加載時機(jī)
- 屬性和方法流程一樣,存在本類的
property_list_t中 - 協(xié)議不會存在申明的類中,而是遵守協(xié)議的類中的
protocol_list_t中 - 變量在
ro的ivars中,屬性也會生成_屬性名存在ivars中

