在上一篇文章iOS類的加載(上)中,我們知道了類是如何從Mach-O中加載到內(nèi)存,這篇文章我們來分析分類是如何加載到類里面的,以及分類和類的搭配使用情況
前提
在main函數(shù)中定義LGPerson的分類LG
#pragma mark -- LGPerson的分類
@interface LGPerson (LG)
@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cat_age;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod;
@end
@implementation LGPerson (LG)
- (void)cate_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod{
NSLog(@"%s",__func__);
}
@end
下面我們將通過三種方式來探索分類的本質(zhì)
- 【方式一】
clang - 【方式二】
Xcode文檔搜索Category - 【方式三】
objc源碼搜索category_t
【方式一】clang
clang -rewrite-objc main.m -o main.cpp命令查看底層編譯
分類的類型是
_category_t-
分類的倒數(shù)第二個0,表示的是
沒有協(xié)議,所以賦值為0
image.png -
搜索
struct _category_t,如下所示- 其中有兩個
method_list_t,代表實例方法和類方法
方法列表
- 其中有兩個
-
搜索
_CATEGORY_INSTANCE_METHODS_LGPerson_,
image.png
其中有3個方法,格式為:sel+簽名+地址,是method_t結(jié)構(gòu)體的屬性即key
image.png -
搜索
method_t,其中對應(yīng)關(guān)系如下- name == sel
- type == 方法簽名
-
imp == 函數(shù)地址
image.png
-
查看
_prop_list_t,我們發(fā)現(xiàn)在分類中定義了的屬性,但是在底層編譯中并沒有看到屬性,如下圖所示,這是因為分類中定義的屬性沒有對應(yīng)的set、get方法,但是我們可以通過關(guān)聯(lián)對象來進(jìn)行設(shè)置
image.png
【方式二】Xcode文檔搜索Category
command + shift + O打開Xcode文檔搜索Category

【方式三】通過objc源碼搜索category_t

分類的加載
前提
創(chuàng)建兩個分類LGA、LGB

上一篇文章iOS類的加載(上)中提到了分類的加載順序是越晚加進(jìn)來,越在前面
查看methodizeClass的源碼實現(xiàn),可以發(fā)現(xiàn)類的數(shù)據(jù)和分類的數(shù)據(jù)是分開處理的,是因為在編譯階段就已經(jīng)確定好了方法的歸屬位置(實例方法在類中,類方法在元類中),而分類是后面才加進(jìn)來的

其中分類是需要通過attatchToClass方法添加到類里面,分為下面三步
- 分類數(shù)據(jù)
加載時機:根據(jù)類和分類是否實現(xiàn)load方法來區(qū)分 -
attachCategories準(zhǔn)備分類數(shù)據(jù) -
attachLists將分類數(shù)據(jù)添加到主類中
load_images
load_images方法的主要作用是加載鏡像文件,其中最重要的有兩個方法:prepare_load_methods(加載) 和 call_load_methods(調(diào)用)
load_images原理圖
- 從所有的
非懶加載類和分類中的+load分別添加到表中 - 調(diào)用
類和分類的+load方法
load_images原理圖
load_images調(diào)用過程

分類的加載時機
我們在LGPerson和LGA、LGB中均實現(xiàn)+load方法
我們通過第二步數(shù)據(jù)反推第一步加載時機
在上一篇文章中我們知道,在attachCategories方法時,必然有分類數(shù)據(jù)的加載,因此我們可以反過來查看什么時候調(diào)用了attachCategories,全局搜索發(fā)現(xiàn)有兩個方法調(diào)用
-
load_categories_nolock方法
load_categories_nolock -
addToClass方法中,這里經(jīng)過調(diào)試發(fā)現(xiàn),從來不會進(jìn)到if流程中,除非加載兩次,一般的類一般只會加載一次
addToClass -
運行objc代碼,打印日志,通過日志可以發(fā)現(xiàn)
addToClass方法的下一步就是load_categories_nolock方法就是加載分類數(shù)據(jù)
image.png -
全局搜索
load_categories_nolock的調(diào)用,有兩次調(diào)用- 一次在
loadAllCategories方法中
image.png
- 一次在
-
一次在
_read_images方法中
image.png -
但是經(jīng)過調(diào)試發(fā)現(xiàn),是不會走
_read_images方法中的if流程的,而是走的loadAllCategories方法中的
image.png -
全局搜索查看
loadAllCategories的調(diào)用,發(fā)現(xiàn)是在load_images時調(diào)用的
image.png
通過堆棧信息分析
- 在
attachCategories中加自定義邏輯的斷點,bt查看堆棧信息
image.png
所以綜上所述,該情況下的分類的數(shù)據(jù)加載時機的反推路徑為:attachCategories -> load_categories_nolock -> loadAllCategories -> load_images
而我們的分類加載正常的流程的路徑為:realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories

LGPerson+分類LGA實現(xiàn)+load,分類LGB不實現(xiàn)+load方法
-
斷點定在
attachCategories中加自定義邏輯部分,一步步往下執(zhí)行
image.png -
繼續(xù)往下執(zhí)行,會再次來到
attachCategories方法中斷住
image.png
總結(jié):只要有一個分類是非懶加載分類,那么所有的分類都會被標(biāo)記位非懶加載分類,意思就是加載一次 已經(jīng)開辟了rwe,就不會再次懶加載,重新去處理 主類
分類和類的搭配使用
通過上面的兩個例子,我們可以大致將類 和 分類 是否實現(xiàn)+load的情況分為4種
【情況1】非懶加載類 + 非懶加載分類
【情況2】非懶加載類 + 懶加載分類
【情況3】懶加載類 + 懶加載分類
【情況4】懶加載類 + 非懶加載分類
非懶加載類 與 非懶加載分類
主類和分類都實現(xiàn)了+load方法,綜合前面的分析可以得出下面結(jié)論
-
類是通過
_getObjc2NonlazyClassList加載數(shù)據(jù)的,即ro、rw的操作,對rwe初始化賦值是在extAlloc方法中- 調(diào)用路徑為:
map_images -> map_images_nolock -> _read_images -> readClass -> _getObjc2NonlazyClassList -> realizeClassWithoutSwift -> methodizeClass -> attachToClass,此時的mlists是一維數(shù)組,然后走到load_images部分
- 調(diào)用路徑為:
-
分類的數(shù)據(jù)加載是通過
load_images加載到類中的-
load_images --> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists,此時的mlists是二維數(shù)組
-
下面為源碼中調(diào)試的打印日志

非懶加載類 與 懶加載分類
主類實現(xiàn)了+load方法,分類未實現(xiàn)+load方法
-
打開
realizeClassWithoutSwift中的自定義斷點,看一下ro-
查看kc_ro
image.png -
p
kc_ro->baseMethodList
image.png -
p
$1.get(0) 、$1.get(1) 、$1.get(2) 、$1.get(3) 、$1.get(4)
image.png -
p
$1.get(5)、$1.get(10)
image.png
-
-
來到
prepareMethodLists的for循環(huán)部分
image.png -
來到
fixupMethodList方法中的if (sort) {部分- 其中
SortBySELAddress的源碼實現(xiàn)如下:根據(jù)名字的地址進(jìn)行排序
image.png
- 其中
-
走到
mlist->setFixedUp();,在讀取list
image.png
image.png
通過打印發(fā)現(xiàn),僅對同名方法進(jìn)行了排序,而分類中的其他方法是不需要排序的,其中imp地址是有序的(從小到大) -- fixupMethodList中的排序只針對 name 地址進(jìn)行排序
-
不加任何斷點,運行程序,獲取打印日志
image.png
非懶加載類 與 懶加載分類的數(shù)據(jù)加載 總結(jié):
- 類 和 分類的加載是在
read_images就加載數(shù)據(jù)了 - 其中data數(shù)據(jù)在
編譯時期就已經(jīng)完成了
懶加載類 與 懶加載分類
主類和分類均未實現(xiàn)+load方法
-
不加任何斷點,運行程序,獲取打印日志
image.png
其中realizeClassMaybeSwiftMaybeRelock是消息流程中慢速查找中有的函數(shù),即在第一次調(diào)用消息時才有的函數(shù)
- 在
readClass斷住,然后讀取kc_ro,即讀取整個data
image.png
此時的baseMethodList的count還是16,說明也是從data中讀取出來的,所以不需要經(jīng)過一層緩慢的load_images加載進(jìn)來
總結(jié):懶加載類 與 懶加載分類的數(shù)據(jù)加載是在消息第一次調(diào)用時加載
懶加載類 與 非懶加載分類
主類未實現(xiàn)+load方法, 分類實現(xiàn)了+load方法
-
不加任何斷點,運行程序,獲取打印日志
image.png -
在打印的日志中沒有看到
load_categories_nolock方法,查看attachCategories -- extAlloc -- attachToClass -- attachCategories,在attachToClass中加斷點
image.png -
在
readClass方法中斷住,查看kc_ro
image.png
其中baseMethodList的count是8個,打印看看:對象方法3個+屬性的setget方法共4個+1個cxx方法 ,即 現(xiàn)在只有主類的數(shù)據(jù)

-
查看kc_ro結(jié)構(gòu)
image.png -
打印日志
image.png -
為了調(diào)試分類的數(shù)據(jù)加載, 繼續(xù)往下執(zhí)行,
bt查看堆棧:load_images -> loadAllCategories -> load_categories_nolock
image.png
總結(jié): 懶加載類 + 非懶加載分類的數(shù)據(jù)加載,只要分類實現(xiàn)了load,會迫使主類提前加載,即 主類 強行轉(zhuǎn)換為 非懶加載類
總結(jié)
通過上面的分析可知:分類的本質(zhì)是_category_t
- 有兩個屬性:
name(類名)和cls(類對象) - 有兩個
method_list_t方法列表:分類中實現(xiàn)的類方法和實例方法 - 一個
protocol_list_t協(xié)議列表:分類中實現(xiàn)的協(xié)議 - 一個
prop_list_t屬性列表:分類中定義的屬性,一般通過關(guān)聯(lián)對象實現(xiàn) - 分類中的屬性是沒有
set、get方法
類和分類搭配使用,其數(shù)據(jù)的加載時機總結(jié)如下:
- 【情況1】
非懶加載類 + 非懶加載分類,其數(shù)據(jù)的加載在load_images方法中,首先對類進(jìn)行加載,然后把分類的信息貼到類中
-【情況2】非懶加載類 + 懶加載分類,其數(shù)據(jù)加載在read_image就加載數(shù)據(jù),數(shù)據(jù)來自data,data在編譯時期就已經(jīng)完成,即data中除了類的數(shù)據(jù),還有分類的數(shù)據(jù),與類綁定在一起
-【情況3】懶加載類 + 懶加載分類 ,其數(shù)據(jù)加載推遲到 第一次消息時,數(shù)據(jù)同樣來自data,data在編譯時期就已經(jīng)完成
-【情況4】懶加載類 + 非懶加載分類 ,只要分類實現(xiàn)了load,會迫使主類提前加載,即在_read_images中不會對類做實現(xiàn)操作,需要在load_images方法中觸發(fā)類的數(shù)據(jù)加載,即rwe初始化,同時加載分類數(shù)據(jù)


































