在上一篇文章iOS-底層原理 17:類的加載(上)中,理解了類是如何從Mach-O加載到內(nèi)存
中,這次我們來(lái)解釋下分類
是如何加載
到類
中的,以及分類和類搭配使用
的情況
分類的本質(zhì)
前提:在main中定義LGperson的分類LG
探索分類的本質(zhì),有以下三種方式
- 【方式一】通過(guò)
clang
- 【方式二】通過(guò)
Xcode
文檔搜索Category
- 【方式三】通過(guò)
objc
源碼搜索category_t
方式一:通過(guò)clang
-
【方式一】
clang -rewrite-objc main.m -o main.cpp
查看底層編譯,即 main.cpp,- 其中
分類
的 類型是_category_t
- 分類的倒數(shù)第二個(gè)0,表示的是
沒(méi)有協(xié)議
,所以賦值為0
image
- 其中
-
搜索
struct _category_t
,如下所示- 其中有兩個(gè)method_list_t,分別表示
實(shí)例方法
和類方法
image
- 其中有兩個(gè)method_list_t,分別表示
-
搜索
_CATEGORY_INSTANCE_METHODS_LGPerson_
,找到其底層實(shí)現(xiàn)
image
其中有3個(gè)方法,格式為:sel+簽名+地址
,是method_t
結(jié)構(gòu)體的屬性即key
image -
搜索
method_t
,其中對(duì)應(yīng)關(guān)系如下-
name
對(duì)應(yīng)sel
-
type
對(duì)應(yīng)方法簽名
-
imp
對(duì)應(yīng)函數(shù)地址
image
-
同時(shí),我們發(fā)現(xiàn)了一個(gè)問(wèn)題:查看看_prop_list_t
,明明分類
中定義了屬性
,但是在底層編譯中并沒(méi)有看到屬性,如下圖所示,這是因?yàn)?code>分類中定義的屬性沒(méi)有相應(yīng)的set、get方法,我們可以通過(guò)關(guān)聯(lián)對(duì)象
來(lái)設(shè)置(關(guān)于如何設(shè)置關(guān)聯(lián)對(duì)象
,我們將在后續(xù)的擴(kuò)展中進(jìn)行說(shuō)明)
方式二:通過(guò)Xcode文檔搜索 Category
如果不會(huì)clang
,可以通過(guò)Xcode
文檔搜索 Category
方式三:通過(guò)objc源碼搜索 category_t
還可以通過(guò)objc源碼搜索category_t
類型
總結(jié)
綜上所述,分類的本質(zhì)
是一個(gè)_category_t
類型
有兩個(gè)屬性:
name(類的名稱)
和cls(類對(duì)象)
有兩個(gè)
method_list_t
類型的方法列表,表示分類中實(shí)現(xiàn)的實(shí)例方法+類方法
一個(gè)
protocol_list_t
類型的協(xié)議列表,表示分類中實(shí)現(xiàn)的協(xié)議一個(gè)
prop_list_t
類型的屬性列表,表示分類中定義的屬性,一般在分類中添加的屬性都是通過(guò)關(guān)聯(lián)對(duì)象
來(lái)實(shí)現(xiàn)需要注意的是,分類中的
屬性
是沒(méi)有set、get
方法
分類的加載
前提:創(chuàng)建LGPerson的兩個(gè)分類:LGA、LGB
在上一篇iOS-底層原理 17:類的加載(上)文章中的realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_categories_nolock -> extAlloc ->attachCategories
中提及了rwe的加載,其中分析了分類的data
數(shù)據(jù) 時(shí)如何 加載到類
中的,且分類的加載順序是:LGA -> LGB
的順序加載到類中,即越晚加進(jìn)來(lái),越在前面
其中查看methodizeClass
的源碼實(shí)現(xiàn),可以發(fā)現(xiàn)類的數(shù)據(jù)
和 分類的數(shù)據(jù)
是分開(kāi)處理的,主要是因?yàn)樵?code>編譯階段,就已經(jīng)確定好了方法的歸屬位置
(即實(shí)例方法
存儲(chǔ)在類
中,類方法
存儲(chǔ)在元類
中),而分類
是后面才加進(jìn)來(lái)的
其中分類需要通過(guò)attatchToClass
添加到類,然后才能在外界進(jìn)行使用,在此過(guò)程,我們已經(jīng)知道了分類加載三步驟的后面兩個(gè)步驟,分類的加載主要分為3步:
分類數(shù)據(jù)
加載時(shí)機(jī)
:根據(jù)類和分類是否實(shí)現(xiàn)load方法
來(lái)區(qū)分不同的時(shí)機(jī)attachCategories
準(zhǔn)備分類數(shù)據(jù)
attachLists
將分類數(shù)據(jù)添加到主類
中
分類的加載時(shí)機(jī)
下面我們來(lái)探索分類數(shù)據(jù)的加載時(shí)機(jī)
,以主類
LGPerson + 分類
LGA、LGB 均實(shí)現(xiàn)+load
方法為例
通過(guò)第二步數(shù)據(jù)準(zhǔn)備反推第一步的加載時(shí)機(jī)
-
通過(guò)上一篇文章我們了解到,在走到
attachCategories
方法時(shí),必然會(huì)有分類數(shù)據(jù)的加載
,可以通過(guò)反推法
查看 在什么時(shí)候調(diào)用attachCategories
的,通過(guò)查找,有兩個(gè)方法中調(diào)用-
load_categories_nolock
方法中
image -
addToClass
方法中,這里經(jīng)過(guò)調(diào)試發(fā)現(xiàn),從來(lái)不會(huì)進(jìn)到if流程中,除非加載兩次,一般的類一般只會(huì)加載一次
image
-
-
不加任何斷點(diǎn),運(yùn)行objc代碼,可以得出以下打印日志,通過(guò)日志可以發(fā)現(xiàn)
addToClass
方法的下一步就是load_categories_nolock
方法就是加載分類數(shù)據(jù)
image -
全局搜索
load_categories_nolock
的調(diào)用,有兩次調(diào)用- 一次在
loadAllCategories
方法中
image - 一次在
_read_images
方法中
image
- 一次在
-
但是經(jīng)過(guò)調(diào)試發(fā)現(xiàn),是不會(huì)走
_read_images
方法中的if流程的,而是走的loadAllCategories
方法中的
image -
全局搜索查看
loadAllCategories
的調(diào)用,發(fā)現(xiàn)是在load_images
時(shí)調(diào)用的
image
通過(guò)堆棧信息分析
- 在
attachCategories
中加自定義邏輯的斷點(diǎn),bt
查看堆棧信息
image
所以綜上所述,該情況下的分類的數(shù)據(jù)加載時(shí)機(jī)
的反推路徑
為:attachCategories -> load_categories_nolock -> loadAllCategories -> load_images
而我們的分類加載正常的流程的路徑為:realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories
其中正向和反向的流程如下圖所示:
我們?cè)賮?lái)看一種情況:主類+分類LGA實(shí)現(xiàn)+load,分類LGB不實(shí)現(xiàn)+load方法
-
斷點(diǎn)定在
attachCategories
中加自定義邏輯部分,一步步往下執(zhí)行p entry.cat
-
p *$0
image
-
繼續(xù)往下執(zhí)行,會(huì)再次來(lái)到
attachCategories
方法中斷住p entry.cat
-
p *$0
image
總結(jié):只要有一個(gè)分類是非懶加載分類,那么所有的分類都會(huì)被標(biāo)記位非懶加載分類
,意思就是加載一次 已經(jīng)開(kāi)辟了rwe
,就不會(huì)再次懶加載,重新去處理 LGPerson
分類和類的搭配使用
通過(guò)上面的兩個(gè)例子,我們可以大致將類 和 分類 是否實(shí)現(xiàn)+load的情況分為4種
類+分類 | |||
---|---|---|---|
分類實(shí)現(xiàn)+load | 分類未實(shí)現(xiàn)+load | ||
類實(shí)現(xiàn)+load | 非懶加載類+非懶加載分類 | 非懶加載類+懶加載分類 | |
類未實(shí)現(xiàn)+load | 懶加載類+非懶加載分類 | 懶加載類+懶加載分類 |
【情況1】
非懶加載類 + 非懶加載分類
【情況2】
非懶加載類 + 懶加載分類
【情況3】
懶加載類 + 懶加載分類
【情況4】
懶加載類 + 非懶加載分類
非懶加載類 與 非懶加載分類
即主類實(shí)現(xiàn)了+load方法,分類同樣實(shí)現(xiàn)了+load方法
,在前文分類的加載時(shí)機(jī)時(shí),我們已經(jīng)分析過(guò)這種情況,所以可以直接得出結(jié)論,這種情況下
類的數(shù)據(jù)加載是通過(guò)
_getObjc2NonlazyClassList
加載,即ro、rw的操作,對(duì)rwe
賦值初始化,是在extAlloc
方法中分類的數(shù)據(jù)加載
是通過(guò)load_images
加載到類中的
其調(diào)用路徑為:
map_images -> map_images_nolock -> _read_images -> readClass -> _getObjc2NonlazyClassList -> realizeClassWithoutSwift -> methodizeClass -> attachToClass
,此時(shí)的mlists
是一維數(shù)組,然后走到load_images
部分load_images --> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists
,此時(shí)的mlists
是二維數(shù)組
下面為源碼中調(diào)試的打印日志
非懶加載類 與 懶加載分類
即主類實(shí)現(xiàn)了+load方法,分類未實(shí)現(xiàn)+load方法
-
打開(kāi)
realizeClassWithoutSwift
中的自定義斷點(diǎn),看一下ro- 查看
kc_ro
image -
p kc_ro->baseMethodList
image - p
1.get(4)
image - p
1.get(10)
image
從上面的打印輸出可以看出,方法的順序是LGB—LGA-LGPerson類
,此時(shí)分類已經(jīng) 加載進(jìn)來(lái)了,但是還沒(méi)有排序,說(shuō)明在沒(méi)有進(jìn)行非懶加載時(shí),通過(guò)cls->data
讀取Mach-O
數(shù)據(jù)時(shí),數(shù)據(jù)就已經(jīng)編譯進(jìn)來(lái)了,不需要運(yùn)行時(shí)添加進(jìn)去
- 查看
-
來(lái)到
methodizeClass
方法中斷點(diǎn)部分p list
-
p $0->get(0)
-p $0->get(5)
image
-
來(lái)到
prepareMethodLists
的for循環(huán)部分- p addedLists[0]
- p addedLists[1]
- p *$1
-
p *$2
image
-
來(lái)到
fixupMethodList
方法中的if (sort) {
部分- 其中
SortBySELAddress
的源碼實(shí)現(xiàn)如下:根據(jù)名字的地址進(jìn)行排序
image
- 其中
-
走到
mlist->setFixedUp();
,在讀取listp mlist
-
p $7->get(0)
~p $7->get(3)
image -
p $7->get(4)
~p $7->get(6)
image
通過(guò)打印發(fā)現(xiàn),僅對(duì)同名方法進(jìn)行了排序
,而分類中的其他方法是不需要排序的,其你imp地址是有序的(從小到大) --fixupMethodList
中的排序只針對(duì) name 地址進(jìn)行排序
image
-
不加任何斷點(diǎn),運(yùn)行程序,獲取打印日志
image
總結(jié):非懶加載類 與 懶加載分類
的數(shù)據(jù)加載,有如下結(jié)論:
類 和 分類的加載
是在read_images
就加載數(shù)據(jù)了其中
data數(shù)據(jù)
在編譯時(shí)期
就已經(jīng)完成了
懶加載類 與 懶加載分類
即主類和分類均未實(shí)現(xiàn)+load方法
-
不加任何斷點(diǎn),運(yùn)行程序,獲取打印日志
image
其中realizeClassMaybeSwiftMaybeRelock
是消息流程中慢速查找中有的函數(shù),即在第一次調(diào)用消息時(shí)
才有的函數(shù) -
在
readClass
斷住,然后讀取kc_ro
,即讀取整個(gè)data
image
此時(shí)的baseMethodList
的count
還是16,說(shuō)明也是從data中讀取出來(lái)
的,所以不需要經(jīng)過(guò)一層緩慢的load_images
加載進(jìn)來(lái)
總結(jié):懶加載類 與 懶加載分類
的數(shù)據(jù)加載
是在消息第一次調(diào)用
時(shí)記載
懶加載類 與 非懶加載分類
即主類未實(shí)現(xiàn)+load方法, 分類實(shí)現(xiàn)了+load方法
-
不加任何斷點(diǎn),運(yùn)行程序,獲取打印日志
image -
在打印的日志中沒(méi)有看到
load_categories_nolock
方法,查看attachCategories -- extAlloc -- attachToClass -- attachCategories
,在attachToClass中加斷點(diǎn)
image -
在
readClass
方法中斷住,查看kc_ro
image
其中baseMethodList
的count是8個(gè),打印看看:對(duì)象方法3個(gè)+屬性的setget方法共4個(gè)+1個(gè)cxx方法 ,即 現(xiàn)在只有主類的數(shù)據(jù)
image-
查看kc_ro結(jié)構(gòu)
image - p kc_ro->baseMethodList
- p
0->get(3)、p $0->get(7)
image
-
-
為了調(diào)試分類的數(shù)據(jù)加載, 繼續(xù)往下執(zhí)行,
bt
查看堆棧:load_images -> loadAllCategories -> load_categories_nolock
image
總結(jié):懶加載類 + 非懶加載分類
的數(shù)據(jù)加載,只要分類實(shí)現(xiàn)了load,會(huì)迫使主類提前加載
,即 主類 強(qiáng)行轉(zhuǎn)換為 非懶加載類樣式
總結(jié)
類和分類
搭配使用,其數(shù)據(jù)的加載時(shí)機(jī)
總結(jié)如下:
【情況1】
非懶加載類 + 非懶加載分類
,其數(shù)據(jù)的加載在load_images
方法中,首先對(duì)類進(jìn)行加載,然后把分類的信息貼到類中【情況2】
非懶加載類 + 懶加載分類
,其數(shù)據(jù)加載在read_image
就加載數(shù)據(jù),數(shù)據(jù)來(lái)自data
,data在編譯時(shí)期
就已經(jīng)完成,即data中除了類的數(shù)據(jù),還有分類的數(shù)據(jù),與類綁定在一起【情況3】
懶加載類 + 懶加載分類
,其數(shù)據(jù)加載推遲到 第一次消息
時(shí),數(shù)據(jù)同樣來(lái)自data
,data在編譯時(shí)期
就已經(jīng)完成【情況4】
懶加載類 + 非懶加載分類
,只要分類實(shí)現(xiàn)了load,會(huì)迫使主類提前加載
,即在_read_images
中不會(huì)對(duì)類做實(shí)現(xiàn)操作,需要在load_images
方法中觸發(fā)類的數(shù)據(jù)加載,即rwe初始化
,同時(shí)加載分類數(shù)據(jù)
如下圖所示
補(bǔ)充:load_images原理分析
load_images
方法的主要作用是加載鏡像文件,其中最重要的有兩個(gè)方法:prepare_load_methods
(加載) 和 call_load_methods
(調(diào)用)
-
進(jìn)入
load_images
源碼實(shí)現(xiàn)
load_images原理分析-1 -
進(jìn)入
prepare_load_methods
源碼-
進(jìn)入
_getObjc2NonlazyClassList -> schedule_class_load
源碼,這里主要是根據(jù)類的繼承鏈遞歸調(diào)用獲取load
,直到cls不存在才結(jié)束遞歸,目的是為了確保父類的load優(yōu)先加載
load_images原理分析-2 -
進(jìn)入
add_class_to_loadable_list
,主要是將load方法和cls類名
一起加到loadable_classes
表中
load_images原理分析-3- 進(jìn)入
getLoadMethod
,主要是獲取方法的sel為load
的方法
load_images原理分析-4
- 進(jìn)入
-
_getObjc2NonlazyCategoryList -> realizeClassWithoutSwift -> add_category_to_loadable_list
,主要是將非懶加載分類的load方法加入表中
load_images原理分析-5- 進(jìn)入
add_category_to_loadable_list
實(shí)現(xiàn),獲取所有的非懶加載分類中的load方法,將分類名+load
加入表loadable_categories
load_images原理分析-6
- 進(jìn)入
-
-
進(jìn)入
call_load_methods
源碼,主要有3部分操作反復(fù)調(diào)用
類的+load
,直到不再有調(diào)用一次分類的+load
-
如果有類或更多未嘗試的分類,則運(yùn)行更多的+load
load_images原理分析-7 -
進(jìn)入
call_class_loads
,主要是加載類的load方法
load_images原理分析-8
其中load
方法中有兩個(gè)隱藏參數(shù),第一個(gè)為id 即self
,第二個(gè)為sel,即cmd
-
call_category_loads
,主要是加載一次分類的load方法
load_images原理分析-9
綜上所述,load_images
方法整體調(diào)用過(guò)程
及原理圖示
如下
-
調(diào)用過(guò)程圖示
調(diào)用過(guò)程圖示 -
原理圖示
原理圖示
主要分為兩步
- 從所有的
非懶加載類和分類
中的+load分別添加到表
中 - 調(diào)用
類和分類的+load
方法
- 從所有的