iOS底層探索 --- 類的加載(下)

image

在前兩篇文章中稽亏,我們分析了類的加載壶冒。但是在類的加載過(guò)程中,不僅僅是類本身的加載措左,還有分類依痊,類的擴(kuò)展等的加載。下面我們就來(lái)分析以下怎披,分類和類的擴(kuò)展是怎么加載的胸嘁。


一、CPP文件分析分類

首先我們將.m文件轉(zhuǎn)換成CPP文件凉逛,以此來(lái)觀察以下分類在底層是什么樣子的性宏。這里我們?cè)賮?lái)回憶一下,生成CPP文件的兩種終端指令:

  • clang: (這里也可以不要后面的-o xxx.cpp)
$ clang -rewrite-objc xxx.m -o xxx.cpp
  • xcrun
$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc xxx.m -o xxx.cpp

1. 這里我們定義一個(gè)Person類状飞,并創(chuàng)建它的分類:

image

2. 利用終端毫胜,將Person-Jax.m文件轉(zhuǎn)換成CPP文件:

image

3. 查看Person+Jax.cpp文件,探索分類的底層結(jié)構(gòu):

在該文件中诬辈,我們看到了_category_t的底層結(jié)構(gòu)如下:

struct _category_t {
    const char *name;
    struct _class_t *cls;
    const struct _method_list_t *instance_methods;
    const struct _method_list_t *class_methods;
    const struct _protocol_list_t *protocols;
    const struct _prop_list_t *properties;
};
  • 通過(guò)下面的代碼酵使,我們可以推斷出Person-Jax在底層的結(jié)構(gòu):

    image

    其中我們通過(guò)_category_t可以了解到一下信息:

  • 這里還有一條信息,那就是_category_t中的name焙糟,是Jax口渔。(這里大家可能會(huì)有疑問(wèn),既然是Jax穿撮,那Person-Jax里面為什么是Person呢缺脉?因?yàn)楝F(xiàn)在是靜態(tài)編譯,編譯器不知道賦什么值悦穿。所以隨機(jī)給的是Person

  • 通過(guò)_category_t可以看到攻礼,有兩個(gè)_method_list_t

    • instanceMethods:實(shí)例方法列表。
    • classMethods:類方法列表栗柒。
  • _protocol_list_t:協(xié)議列表礁扮。

  • _prop_list_t:屬性列表。(在分類中傍衡,可以定義屬性深员,但是不會(huì)自動(dòng)生成gettersetter方法)

  • 通過(guò)文件中最下方代碼,我們還可以得出一條結(jié)論:分類是存放在__DATA段的__objc_catlist中蛙埂。

    image

我們通過(guò)CPP文件知道了分類在底層是_category_t結(jié)構(gòu)倦畅,這個(gè)時(shí)候我們也可以在源碼中搜索一下,來(lái)對(duì)比一下:

image


二绣的、分類的加載

我們回想一下叠赐,我們?cè)?a target="_blank">iOS底層探索 --- 類的加載(中)的時(shí)候欲账,遇到的methodizeClass嗎?這里有一條官方注釋是這樣寫(xiě)的:Attaches any outstanding categories芭概。也就是說(shuō)赛不,我們的分類是在這里被附加的。那我們就再次探索一下這個(gè)函數(shù)罢洲。

我們會(huì)發(fā)現(xiàn)踢故,方法列表屬性列表惹苗、協(xié)議列表等等殿较,它們的附加都與rwe有關(guān)系:

image

也就是說(shuō),附加的時(shí)候桩蓉,必然要有rwe的存在淋纲。那我們就去找rwe

image

image

在我們進(jìn)入ext()函數(shù)之后,對(duì)于源碼有點(diǎn)迷茫院究。但是下面的extAllocIfNeeded()結(jié)合ext()就有點(diǎn)意思了(根據(jù)字面意思:“需要的情況下洽瞬,alloc ext?”)。整體來(lái)看业汰,也就是說(shuō)伙窃,如果有ext,那就必然能執(zhí)行get样漆,獲得ext对供。(有點(diǎn)繞,大家好好捋一下思路)

這里可以將ext理解為一個(gè)標(biāo)識(shí)符氛濒。我們都知道:

  • roclean memory(ro 是只讀的,不需要的時(shí)候可以清除鹅髓,需要的時(shí)候再?gòu)拇疟P中讀取舞竿。復(fù)制到rw),
  • rwdirty memory(rw是動(dòng)態(tài)分配的窿冯,比如我們的分類里面的數(shù)據(jù)骗奖,是昂貴的)
  • 這里就有一個(gè)問(wèn)題了,不是所有的類都需要rw醒串,也就是說(shuō)ro的數(shù)據(jù)已經(jīng)能夠滿足需求了执桌,這個(gè)時(shí)候就有了rwe的出現(xiàn)。(當(dāng)需要?jiǎng)討B(tài)加載的時(shí)候芜赌,就有一個(gè)標(biāo)識(shí)符ext仰挣;如果沒(méi)有,就普通的從ro里面去獲取缠沈。Extention

2.1 extAllocIfNeeded()

我們來(lái)搜索一下膘壶,extAllocIfNeeded()看一下其在什么地方調(diào)用(截取其中一個(gè)):

image

(rwe是在動(dòng)態(tài)運(yùn)行時(shí)才會(huì)被創(chuàng)建错蝴,這一點(diǎn)可以根據(jù)官方的注釋得到。有興趣的可以看一下``extAllocIfNeeded()`的調(diào)用函數(shù)的注釋或者分析以下颓芭。)

2.2 attachCategories

extAllocIfNeeded()attachCategories中也被調(diào)用了顷锰,由于我們現(xiàn)在分析的是分類,所以我們關(guān)注的重點(diǎn)就是attachCategories亡问。

image

這里面的auto rwe = cls->data()->extAllocIfNeeded();同時(shí)也可以證明官紫,rwe是通過(guò)extAllocIfNeeded()來(lái)獲取的。

這里大家對(duì)比一下州藕,extAllocIfNeeded()的調(diào)用對(duì)象是不同的束世,上面是rw,這里是cls->data()慎框;這里大家不要誤解良狈,cls->data()的返回值是class_rw_t *類型的。

image

看到bits不知道大家有沒(méi)有熟悉的感覺(jué)笨枯,沒(méi)錯(cuò)薪丁,我們?cè)?a target="_blank">iOS底層探索 --- 類的結(jié)構(gòu)探索(上)里面探索過(guò):
image

  • attachCategories
    這里我們?nèi)炙阉饕幌拢匆幌孪诰诸惖募虞d在哪里被調(diào)用严嗜。
    搜索下來(lái),有兩處調(diào)用:

    • attachToClass -> attachCategories
    • load_categories_nolock -> attachCategories
    image

    image

既然有兩個(gè)地方調(diào)用了attachCategories洲敢,那我們就通過(guò)斷點(diǎn)調(diào)試漫玄,一個(gè)一個(gè)的分析。

2.2.1 attachToClass

同樣的我們?nèi)炙阉?code>attachToClass压彭,發(fā)現(xiàn)其只在methodizeClass中有調(diào)用:

image

雖然有三處調(diào)用睦优,但是其中兩處的調(diào)用,受previously(函數(shù)中的一個(gè)判斷條件) 影響壮不。

  • 這里的previously來(lái)自于realizeClassWithoutSwift(static Class realizeClassWithoutSwift(Class cls, Class previously));
  • realizeClassWithoutSwift是我們探索過(guò)的汗盘,在_read_images中被調(diào)用,而previously傳入的是nil询一。因此在methodizeClass中隐孽,只有一次會(huì)被調(diào)用。(這里大家會(huì)有疑問(wèn)健蕊,既然傳入的是nil菱阵,為什么還要多此一舉;其實(shí)這是一個(gè)備用參數(shù)缩功,方便調(diào)節(jié)用的晴及。)

也就是說(shuō)我們只需要研究:

objc::unattachedCategories.attachToClass(cls, cls,
                                             isMeta ? ATTACH_METACLASS : ATTACH_CLASS);

三、分類加載的幾種情況

在上面我們分析了分類的底層結(jié)構(gòu)嫡锌,我們得知分類在底層是以結(jié)構(gòu)體的形式存在抗俄。那么我們接下來(lái)探索一下分類的加載脆丁。

在這之前,我們補(bǔ)充一個(gè)之前沒(méi)有明確說(shuō)明的知識(shí)點(diǎn):懶加載類非懶加載類动雹。

在iOS中槽卫,為了提高對(duì)類的處理效率和性能,會(huì)對(duì)類進(jìn)行識(shí)別胰蝠。當(dāng)類需要使用的時(shí)候歼培,系統(tǒng)才會(huì)對(duì)類進(jìn)行實(shí)現(xiàn);如果沒(méi)有使用就不會(huì)實(shí)現(xiàn)茸塞。

像這種需要實(shí)現(xiàn)才進(jìn)行加載的類躲庄,被稱為懶加載類;反之钾虐,無(wú)論是否需要實(shí)現(xiàn)都進(jìn)行加載的類噪窘,被稱為非懶加載類。(我們?nèi)粘i_(kāi)發(fā)中效扫,通過(guò)XCode創(chuàng)建的類倔监,默認(rèn)都是懶加載類

一般情況下,我們可以通過(guò)+load方法菌仁,來(lái)調(diào)整我們自己實(shí)現(xiàn)的類浩习。自定義類實(shí)現(xiàn)+load方法,就可以變?yōu)?code>非懶加載類济丘。因?yàn)?code>+load方法的調(diào)用是在main之前的谱秽。

那么此時(shí)關(guān)于分類的加載我們就有四種情況:

  • 1、主類分類 都實(shí)現(xiàn)+load方法摹迷。
  • 2疟赊、主類實(shí)現(xiàn)+load方法,分類不實(shí)現(xiàn)峡碉。
  • 3听绳、主類不實(shí)現(xiàn)+load方法,分類實(shí)現(xiàn)异赫。
  • 4、主類分類 都不實(shí)現(xiàn)+load方法头岔。

在下面的探索中塔拳,我們會(huì)在源碼中添加下面這樣的代碼,來(lái)輔助我們做探索峡竣。添加到我們需要探索的函數(shù)中靠抑,部分內(nèi)容根據(jù)各自需求可做改動(dòng):

bool isMeta = cls->isMetaClass();
const char *mangledName = cls->nonlazyMangledName();
if (strcmp(mangledName, "Person") == 0) {
       if (!isMeta) {
           printf("%s -Person....\n",__func__);
       }
}

斷點(diǎn)調(diào)試,在main函數(shù)里面調(diào)用Person

image

對(duì)于測(cè)試類和分類适掰,我們使用下面的:


image

3.1颂碧、主類分類 都實(shí)現(xiàn)+load方法

我們?cè)?code>attachCategories中打上斷點(diǎn)(在我們上面添加的代碼中荠列,規(guī)避系統(tǒng)方法)。

image

這樣我們通過(guò)追蹤斷點(diǎn)载城,得到了如下的函數(shù)調(diào)用棧:
load_images -> loadAllCategories -> load_categories_nolock -> attachCategories

在這個(gè)之前還有 _read_image -> realizeClassWithoutSwift這樣的一個(gè)流程肌似。
因?yàn)樵?code>_read_image中有這樣一段注釋:

image

也就是說(shuō),這里的調(diào)用會(huì)被推遲到第一次load_images調(diào)用之后诉瓦。

  • didInitialAttachCategories
    這里還是要說(shuō)一下這個(gè)變量的川队,didInitialAttachCategories初始化為false:
    image

    load_images里面,有這樣一個(gè)判斷語(yǔ)句:
    image

    相信看到這里睬澡,大家都會(huì)明白固额,為什么是第一次load_images之后才會(huì)執(zhí)行(那段官方注釋)。

3.2 主類實(shí)現(xiàn)+load方法煞聪,分類不實(shí)現(xiàn)

同樣的我們通過(guò)斷點(diǎn)調(diào)試斗躏,得到如下的函數(shù)調(diào)用棧:

_read_image -> realizeClassWithoutSwift -> methodizeClass -> attachToClass

并沒(méi)有走attachCategories

這個(gè)情況與下面的情況類似,看下面的分析昔脯。


3.3 主類不實(shí)現(xiàn)+load方法啄糙,分類實(shí)現(xiàn)

_read_image -> realizeClassWithoutSwift -> methodizeClass -> attachToClass

并沒(méi)有走attachCategories

這里有一個(gè)細(xì)節(jié),當(dāng)前我們的主類并沒(méi)有實(shí)現(xiàn)+load栅干,但是我們?cè)?code>_read_image函數(shù)里面迈套,還是走的非懶加載,這說(shuō)明碱鳞,分類實(shí)現(xiàn)+load之后桑李,主類被迫營(yíng)業(yè)了。(這里大家好好理解一下窿给,分類是針對(duì)主類實(shí)現(xiàn)的贵白。)

image

  • 這里就有疑問(wèn)了,既然沒(méi)有執(zhí)行attachCategories崩泡;那么分類里面的信息怎么加載的呢禁荒?此時(shí)分類非懶加載類,按理說(shuō)是要執(zhí)行attachCategories的呀角撞。

這里我們通過(guò)斷點(diǎn)調(diào)試來(lái)探索一下:

  • 首先我們?cè)?code>realizeClassWithoutSwift函數(shù)里面添加如下代碼呛伴,并添加斷點(diǎn):

    image

  • 然后運(yùn)行工程,斷點(diǎn)調(diào)試谒所,控制臺(tái)操作如下(這里的操作热康,在iOS底層探索 --- 類的結(jié)構(gòu)探索(上)里面我們做過(guò)探索,這里就不再贅述):

    image

注意A恿臁=憔!

通過(guò)控制臺(tái)的打印,我們可以看到奕锌,此時(shí)ro里面已經(jīng)有了分類信息(注意看methodList中著觉,count = 13)。

我們?cè)倩匾幌拢?code>ro是怎么來(lái)的:auto ro = (const class_ro_t *)cls->data();惊暴。
上面講過(guò)饼丘,cls->data()返回的是bits.data()

這也就是說(shuō),ro分類的數(shù)據(jù)缴守,來(lái)自于data葬毫。
上面3.2的情況也是一樣的。


3.4 主類分類 都不實(shí)現(xiàn)+load方法

這種情況下屡穗,前面這些函數(shù)都沒(méi)有調(diào)用贴捡。

推遲到第一次消息發(fā)送的時(shí)候,初始化村砂。


四烂斋、load_categories_nolock

上面我們知道,在我們沒(méi)有實(shí)現(xiàn)+load(懶加載)的情況下础废,分類依然能都從data里面加載汛骂,那這個(gè)時(shí)候分類的數(shù)據(jù)從哪里來(lái)的呢?這個(gè)時(shí)候我們就要去探索一下load_categories_nolock评腺。

  • count從哪里來(lái)的呢帘瞭?
    image

    大家注意看,count的初始值是0蒿讥;那么count是在哪里變化的呢蝶念?(函數(shù)內(nèi)部沒(méi)有count的賦值操作)

其實(shí)我們將這個(gè)代碼塊折疊一下,就清晰了:

image

這就相當(dāng)于一個(gè)block的調(diào)用芋绸,先執(zhí)行下面的代碼媒殉,才會(huì)進(jìn)入上面的代碼塊。

  • catlist
    既然count的值跟catlist有關(guān)系摔敛,那我們就進(jìn)去看一下:
    image

可以看到廷蓉,我們的catlist是從MachO文件中獲取的。

也就是說(shuō)分類也是從MachO中加載進(jìn)來(lái)的马昙。這也就驗(yàn)證了上面桃犬,我們?yōu)槭裁茨軌驈?code>data中獲取分類的數(shù)據(jù)。也就是說(shuō)MachO會(huì)直接的去加載整個(gè)的數(shù)據(jù)結(jié)構(gòu)行楞。

注意:不要隨便的去實(shí)現(xiàn)load方法攒暇,這樣會(huì)打亂MachO的數(shù)據(jù)加載,當(dāng)我們自己去實(shí)現(xiàn)+load方法之后敢伸,就有了上面一大堆的流程(包括其中的一些算法),這是非常耗時(shí)的恒削。像分類中實(shí)現(xiàn)+load方法池颈,就是非常不可取的尾序。


五、多個(gè)分類

如果有多個(gè)分類躯砰,但是分類不完全實(shí)現(xiàn)+load方法每币,主類實(shí)現(xiàn)+load方法。這個(gè)時(shí)候琢歇,會(huì)跟3.2的情況一樣嗎兰怠?

這里我們可以在load_categories_nolock中打一個(gè)斷點(diǎn),看一下count的數(shù)值就知道了李茫。(這樣做的理由是揭保,因?yàn)橛?code>分類實(shí)現(xiàn)了+load,那么就一定會(huì)走load_categories_nolock魄宏;那么我們?cè)谶@個(gè)函數(shù)里面秸侣,看一下在非懶加載類的流程中,有幾個(gè)分類會(huì)走這里宠互,就可以得到我們想知道的答案了味榛。)

  • 首先多添加幾個(gè)分類,其中一個(gè)不實(shí)現(xiàn)+load

    image

  • 斷點(diǎn)調(diào)試

    image

    可以看到予跌,count的數(shù)量是3搏色;說(shuō)明此時(shí)的情況和3.1的情況是一樣的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末券册,一起剝皮案震驚了整個(gè)濱河市频轿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌汁掠,老刑警劉巖略吨,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異考阱,居然都是意外死亡翠忠,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門乞榨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秽之,“玉大人,你說(shuō)我怎么就攤上這事吃既】颊ィ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵鹦倚,是天一觀的道長(zhǎng)河质。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么掀鹅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任散休,我火速辦了婚禮,結(jié)果婚禮上乐尊,老公的妹妹穿的比我還像新娘戚丸。我一直安慰自己,他們只是感情好扔嵌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布限府。 她就那樣靜靜地躺著,像睡著了一般痢缎。 火紅的嫁衣襯著肌膚如雪胁勺。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,624評(píng)論 1 305
  • 那天牺弄,我揣著相機(jī)與錄音姻几,去河邊找鬼。 笑死势告,一個(gè)胖子當(dāng)著我的面吹牛蛇捌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播咱台,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼络拌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了回溺?” 一聲冷哼從身側(cè)響起春贸,我...
    開(kāi)封第一講書(shū)人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤霜大,失蹤者是張志新(化名)和其女友劉穎叠穆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蟀拷,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡车要,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年允粤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片翼岁。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡类垫,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出琅坡,到底是詐尸還是另有隱情悉患,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布榆俺,位于F島的核電站售躁,受9級(jí)特大地震影響坞淮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜陪捷,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一碾盐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧揩局,春花似錦、人聲如沸掀虎。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烹玉。三九已至驰怎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間二打,已是汗流浹背县忌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留继效,地道東北人症杏。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像瑞信,于是被迫代替她去往敵國(guó)和親厉颤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容