[iOS] 類的加載(下)

在之前,理解了類是如何從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 
image.jpeg

從上面圖中可以看出,分類的類型是_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)體的屬性:

image.jpeg

  • 繼續(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:


image.jpeg
2.2 加載過程

在之前介紹類的加載時提到了rwe的加載,其中分析了分類的data 數(shù)據(jù)是如何加載到類的,而且分類的加載順序是:LGA ->LGB的順序加載到類中,即越晚加進來,越在前面。

其中查看了methodizeClass 的源碼實現(xiàn),可以發(fā)現(xiàn)類的數(shù)據(jù)和分類的數(shù)據(jù)是分開處理的,主要是因為在編譯階段,就已經(jīng)確定好了方法的歸屬位置(即實例方法存儲在類中,類方法存儲在元類中),而分類是后面才加進來的。

image.png

其中分類需要通過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

其中正向和反向的流程如下圖所示:


image.png
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ù)來自datadata在編譯時期就已經(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ù)

如下圖所示:


image.png

3.load_images

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

image.jpeg

3.1 perpare_load_methods

進入perpare_load_methods源碼:

image.jpeg

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

image.jpeg

  • 進入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

image.jpeg

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方法
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容