在前兩篇文章中稽亏,我們分析了類的加載壶冒。但是在類的加載過(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)建它的分類:
2. 利用終端毫胜,將Person-Jax.m
文件轉(zhuǎn)換成CPP
文件:
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)生成getter
和setter
方法)-
通過(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)系:
也就是說(shuō),附加的時(shí)候桩蓉,必然要有rwe
的存在淋纲。那我們就去找rwe
:
在我們進(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í)符氛濒。我們都知道:
-
ro
是clean memory
(ro
是只讀的,不需要的時(shí)候可以清除鹅髓,需要的時(shí)候再?gòu)拇疟P中讀取舞竿。復(fù)制到rw
), -
rw
是dirty 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è)):
(
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
亡问。
這里面的auto rwe = cls->data()->extAllocIfNeeded();
同時(shí)也可以證明官紫,rwe
是通過(guò)extAllocIfNeeded()
來(lái)獲取的。
這里大家對(duì)比一下州藕,extAllocIfNeeded()
的調(diào)用對(duì)象是不同的束世,上面是rw
,這里是cls->data()
慎框;這里大家不要誤解良狈,cls->data()
的返回值是class_rw_t *
類型的。
看到
bits
不知道大家有沒(méi)有熟悉的感覺(jué)笨枯,沒(méi)錯(cuò)薪丁,我們?cè)?a target="_blank">iOS底層探索 --- 類的結(jié)構(gòu)探索(上)里面探索過(guò):-
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)用:
雖然有三處調(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
對(duì)于測(cè)試類和分類适掰,我們使用下面的:
3.1颂碧、主類
和 分類
都實(shí)現(xiàn)+load
方法
我們?cè)?code>attachCategories中打上斷點(diǎn)(在我們上面添加的代碼中荠列,規(guī)避系統(tǒng)方法)。
這樣我們通過(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)的贵白。)
- 這里就有疑問(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è)代碼塊折疊一下,就清晰了:
這就相當(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
的情況是一樣的。