在之前,理解了類是如何從
Mach-O加載到內(nèi)存的,這次來了解下分類是如何加載到類中的,以及分類和類搭配使用的情況。
1. 分類的本質(zhì)
- 在 main 中定義 LGPerson 的分類 LG
@interface LGPerson (LG)
@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;
- (void)cate_instanceMethod;
- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
+ (void)cate_classMethod;
@end
@implementation LGPerson (LG)
- (void)cate_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2{
NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod3{
NSLog(@"%s",__func__);
}
+ (void)cate_classMethod{
NSLog(@"%s",__func__);
}
@end
- 使用
clang查看編譯的main.cpp文件:
clang -rewrite-objc main.m -o main.cpp

從上面圖中可以看出,分類的類型是_category_t(倒數(shù)第二個是 0,表示沒有協(xié)議,所以賦值為 0)
-
cpp文件中,搜索struct _category_t,如下所示:
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; 協(xié)議
const struct _prop_list_t *properties; 屬性
};
- 搜索
_CATEGORY_INSTANCE_METHODS_LGPerson_,找到其底層實現(xiàn)
image.jpeg
其中有 3 個方法,格式為sel+簽名+地址,是_objc_method結(jié)構(gòu)體的屬性:

- 繼續(xù)搜索
_objc_method,其中的對應關(guān)系如下:
struct _objc_method {
struct objc_selector * _cmd; 對應 sel
const char *method_type; 對應 方法簽名
void *_imp; 對應函數(shù)地址
};
2. 分類的加載
2.1 創(chuàng)建分類
創(chuàng)建 LGPerson 的兩個分類:LGA 和 LGB:

2.2 加載過程
在之前介紹類的加載時提到了rwe的加載,其中分析了分類的data 數(shù)據(jù)是如何加載到類的,而且分類的加載順序是:LGA ->LGB的順序加載到類中,即越晚加進來,越在前面。
其中查看了methodizeClass 的源碼實現(xiàn),可以發(fā)現(xiàn)類的數(shù)據(jù)和分類的數(shù)據(jù)是分開處理的,主要是因為在編譯階段,就已經(jīng)確定好了方法的歸屬位置(即實例方法存儲在類中,類方法存儲在元類中),而分類是后面才加進來的。

其中分類需要通過attatchToClass添加到類,然后才能在外界進行使用,在此過程,我們已經(jīng)知道了分類加載步驟的后面兩個步驟,分類的加載主要分為 3 步:
- 分類數(shù)據(jù)加載時機:根據(jù)類和分類是否實現(xiàn) load 方法來區(qū)分不同的時機
-
attachCategories準備分類數(shù)據(jù) -
attachLists將分類數(shù)據(jù)添加到主類中
2.3 分類的加載時機
2.3.1 主類 LGPerson + 分類 LGA\LGB 均實現(xiàn)+load 方法
下面我們來探索一下分類數(shù)據(jù)的加載時機,以主類 LGPerson + 分類 LGA\LGB 均實現(xiàn)+load 方法為例。
通過第二步反推第一步的加載時機
在之前我們了解到,在走到 attachCategories 方法時,必然會有分類數(shù)據(jù)的加載,可以通過反推法查看在什么時候調(diào)用 attachCategories 的,通過查找,有兩個方法中調(diào)用:
-
load_categories_nolock方法中
image.jpeg -
addToClass方法中,這里經(jīng)過調(diào)試,在主類和分類都實現(xiàn)load方法時,是不會走到 if 流程中的:
image.jpeg -
不加任何斷點,運行
objc代碼,可以得出以下打印日志,通過日志可以發(fā)現(xiàn)addToClass方法的下一步就是load_categories_nolock方法就是加載分類數(shù)據(jù):
image.jpeg -
全局搜索
load_categories_nolock的調(diào)用,有兩次調(diào)用-
一次在
loadAllCategories方法中
image.jpeg -
一次在
_read_images方法中
image.jpeg
-
-
但是經(jīng)過調(diào)試發(fā)現(xiàn),是不會走
_read_images方法中的 if 流程,而是走的loadAllCategories方法中的
image.jpeg -
全局搜索查看
loadAllCategories的調(diào)用,發(fā)現(xiàn)是在load_images時調(diào)用的
image.jpeg -
我們給
attachCategories加自定義邏輯的斷點,bt查看堆棧信息:
image.jpeg
所以綜上所述:該情況下的分類的數(shù)據(jù)加載時機的反推路徑為:
attachCategories -> load_categories_nolock -> loadAllCategories -> load_images
而我們的分類加載正常的流程的路徑為:
realizeClassWithoutSwift -> methodizeClass -> attachToClass ->attachCategories
其中正向和反向的流程如下圖所示:

2.3.2 主類 LGPerson + 分類 LGA 實現(xiàn) +load 方法,LGB 不實現(xiàn) +load 方法
- 斷點定在
attachCategories中加自定義邏輯部分,一步步往下執(zhí)行
image.jpeg - 繼續(xù)往下執(zhí)行,會再次來到
attachCategories方法中斷住
image.png
總結(jié):只要有一個分類是非懶加載分類,那么所有的分類都會被標記為非懶加載分類,意思就是加載一次 已經(jīng)開辟了rwe,就不會再次懶加載,重新去處理LGPerson。
2.4 分類和類的搭配使用
通過上面的兩個例子,我們可以大致將類 和 分類 是否實現(xiàn)+load的情況分為4種
| 類+分類 | . | . |
|---|---|---|
| . | 分類實現(xiàn)+load | 分類未實現(xiàn)+load |
| 類實現(xiàn)+load | 非懶加載類+非懶加載分類 | 非懶加載類+懶加載分類 |
| 類未實現(xiàn)+load | 懶加載類+非懶加載分類 | 懶加載類+懶加載分類 |
2.4.1 非懶加載類 + 非懶加載分類
即主類實現(xiàn)了+load方法,分類同樣實現(xiàn)了+load方法,在前文分類的加載時機時,我們已經(jīng)分析過這種情況,所以可以直接得出結(jié)論,這種情況下:
- 類的數(shù)據(jù)加載是通過
_getObjc2NonlazyClassList加載,即ro、rw的操作,對rwe賦值初始化,是在extAlloc方法中 - 分類的數(shù)據(jù)加載是通過
load_images加載到類中的
2.4.2 非懶加載類 + 懶加載分類
類和分類的加載是在read_image就加載數(shù)據(jù)了,數(shù)據(jù)來自data,data在編譯時期就已經(jīng)完成,即data中除了類的數(shù)據(jù),還有分類的數(shù)據(jù),與類綁定在一起
2.4.3 懶加載類 + 懶加載分類
其數(shù)據(jù)加載推遲到 第一次消息時,數(shù)據(jù)同樣來自data,data在編譯時期就已經(jīng)完成
2.4.4 懶加載類 + 非懶加載分類
只要分類實現(xiàn)了load,會迫使主類提前加載,即主類強行轉(zhuǎn)換為非懶加載類樣式,即在_read_images中不會對類做實現(xiàn)操作,需要在load_images方法中觸發(fā)類的數(shù)據(jù)加載,即rwe初始化,同時加載分類數(shù)據(jù)
如下圖所示:

3.load_images
load_images方法的主要作用是加載鏡像文件,其中最重要的有兩個方法:perpare_load_methods(加載) 和 call_load_methods (調(diào)用)

3.1 perpare_load_methods
進入perpare_load_methods源碼:

-schedule_class_load方法,這里是根據(jù)類的繼承鏈遞歸調(diào)用獲取 load,直到cls不存在才結(jié)束遞歸,目的是為了確保父類的 load優(yōu)先加載:

-
進入
add_class_to_loadable_list,主要是獲取load方法,然后將load方法和cls類名一起加到loadable_classes表中:
image.jpeg -
進入
getLoadMethod,主要是獲取方法的sel 為 load的方法:
image.jpeg
-add_category_to_loadable_list
獲取所有的非懶加載分類中的load方法,將分類名``+load方法加入表loadable_categories

3.2call_load_methods
主要有 3 部分操作:
反復調(diào)用類的
+load,直到不再有調(diào)用一次分類的
+load-
如果有類或更多未嘗試的分類,則運行更多的
+load
image.jpeg -
進入
call_class_loads,主要是加載類的load方法
image.jpeg
注意:方法中有兩個隱藏參數(shù),第一個為id 即self,第二個為sel,即cmd
-
call_category_loads,主要是加載一次分類的 load 方法
image.png
3.3 總結(jié)
綜上所述,load_images方法整體調(diào)用過程及原理圖示如下:
-
調(diào)用過程圖示:
image.jpeg
并且:
父類的 load 方法優(yōu)先于子類調(diào)用
主類的 load 方法優(yōu)先于分類調(diào)用
分類的 load 方法則取決于編譯順序。
-
原理圖示
image.jpeg
主要分為兩步:
- 從所有的
非懶加載類和分類中的+load分別添加到表中 - 調(diào)用類和分類的
+load方法

















