iOS 類的加載(下)

在上一篇文章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

Xcode文檔搜索

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

category_t

分類的加載

前提

創(chuàng)建兩個分類LGA、LGB


image.png

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

methodizeClass

其中分類是需要通過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)用過程
調(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部分
  • 分類的數(shù)據(jù)加載是通過load_images加載到類中的

    • load_images --> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists,此時的mlists是二維數(shù)組

下面為源碼中調(diào)試的打印日志


image.png

非懶加載類 與 懶加載分類

主類實現(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ù)

image.png

  • 查看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ù)

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

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

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