ios應(yīng)用啟動加載過程:類、分類加載

一、類的加載

上一篇我們有分析非懶加載類的加載過程,接下來我們可以在_read_images方法中打印一下,以驗(yàn)證是否加載了我們自己創(chuàng)建的類。

// Realize non-lazy classes (for +load methods and static instances)
    for (EACH_HEADER) {
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            printf("_getObjc2NonlazyClassList Class:%s\n",cls->mangledName());
            if (!cls) continue;

通過打印結(jié)果你會發(fā)現(xiàn),所有實(shí)現(xiàn)了+load方法的類都被正常打印,沒有實(shí)現(xiàn)這個(gè)方法的則沒有打印。

懶加載類和非懶加載類有什么區(qū)別?
看上面代碼注釋:Realize non-lazy classes (for +load methods and static instances)。

1.1 懶加載類的加載

那么懶加載類是什么時(shí)候加載的?我們可以調(diào)用調(diào)用一下Dog類alloc方法

alloc.png

然后進(jìn)入跟入到lookUpImpOrForward方法(這是一個(gè)很重要的方法),alloc是一個(gè)類方法,那此時(shí)cls就應(yīng)該是Dog的元類,inst就是真正的對象。

lookUpImpOrForward.png

但是我們通過LLDB發(fā)現(xiàn),這個(gè)inst還只是一個(gè)地址,說明Dog還沒有被初始化。

ro&rw.png

打斷點(diǎn)然后跟進(jìn)去,最終我們會發(fā)現(xiàn)一個(gè)我們上一篇分析過的方法realizeClassWithoutSwift,這里會進(jìn)行ro/rw、category的操作。
這時(shí)候打印inst(即Dog對象),可以看到rw有值了,說明類已經(jīng)被加載。

1.2 總結(jié)

下面總結(jié)一下懶加載類加載的流程:

  1. 類第一次加載的時(shí)候沒有緩存,所以會來到_class_lookupMethodAndLoadCache3 -> lookUpImpOrForward
  2. lookUpImpOrForward會做一次[圖片上傳中...(懶加載類.png-c5360e-1593754564326-0)]
    判斷,如果沒有實(shí)現(xiàn)(!cls->isRealized()),會來到realizeClassWithoutSwift,也就是最終實(shí)現(xiàn)類加載的地方。

懶加載類非懶加載類的調(diào)用堆棧如下:

懶加載類.png

非懶加載類.png

二、分類的加載

2.1 分類的底層實(shí)現(xiàn)

為了探究分類的底層實(shí)現(xiàn),我們先新建一個(gè)分類:

@interface Dog (test)

@property (nonatomic,strong) NSString *name;
- (void)sayHello;
+ (void)sayByebye;

@end

clang命令重寫一下:

clang -rewrite-objc Dog+test.m -o category.cpp

在文件末尾,我們可以看到底層方法剛好對應(yīng)了分類代碼:

static struct _category_t _OBJC_$_CATEGORY_Dog_$_test __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Dog",
    0, // &OBJC_CLASS_$_Dog,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Dog_$_test,
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_Dog_$_test,
    0,
    (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_Dog_$_test,
};
static void OBJC_CATEGORY_SETUP_$_Dog_$_test(void ) {
    _OBJC_$_CATEGORY_Dog_$_test.cls = &OBJC_CLASS_$_Dog;
}

為了看到分類相對完整的底層實(shí)現(xiàn),建議給新建的分類添加實(shí)例方法、類方法、屬性。

_OBJC_$_CATEGORY_INSTANCE_METHODS_Dog_$_test:實(shí)例方法
_OBJC_$_CATEGORY_CLASS_METHODS_Dog_$_test:類方法
_OBJC_$_PROP_LIST_Dog_$_test:屬性
同時(shí)我們還看到如下代碼:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_Dog_$_test,
};

這表明分類存儲在__DATA段的__objc_catlist section里面。

2.2 分類的定義

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

name : 是分類所關(guān)聯(lián)的類,也就是類的名字,而不是分類的名字
cls : 我們在前面可以看到 clang 重寫后這個(gè)值為 0,但是后面有注釋為 &OBJC_CLASS_$_Dog,也就是我們的類對象的定義,所以這里其實(shí)就是我們要擴(kuò)展的類對象,只是在編譯期這個(gè)值并不存在
instanceMethods : 分類上存儲的實(shí)例方法
classMethods :分類上存儲的類方法
protocols :分類所實(shí)現(xiàn)的協(xié)議
instanceProperties :分類所定義的實(shí)例屬性,不過我們一般在分類中添加屬性都是通過關(guān)聯(lián)對象來實(shí)現(xiàn)的
_classProperties :分類所定義的類屬性。這里有一行注釋:Fields below this point are not always present on disk. 下面的內(nèi)容并不是一直在磁盤上保存,也就是說他是一個(gè)私有屬性,但并不是一直都存在的。

三、分類的加載

前面我們已經(jīng)知道了懶加載類和非懶加載類,分類也分為懶加載和非懶加載。

3.1 懶加載類 & 懶加載分類

從前面的分析,我們知道分類的加載過程:realizeClassWithoutSwift -> methodizeClass -> methodizeClass -> attachCategories。

attachCategories.png

通過斷點(diǎn),我們發(fā)現(xiàn)catsNULL,即unattachedCategoriesForClass并沒有獲取到分類。但是我們打印rorw,發(fā)現(xiàn)我們methods是有內(nèi)容的(sayByebye是我們手動添加到分類的類方法)。
也就是說,如果是懶加載類,并且分類也是懶加載,那么分類在編譯時(shí)直接加載到了類的 ro 里面,然后在運(yùn)行時(shí)被拷貝到了類的 rw 里面。

3.2 總結(jié)

分類的加載可以簡單以是否實(shí)現(xiàn)load方法分類:

  1. 懶加載:編譯時(shí)確定
  2. 非懶加載:運(yùn)行時(shí)確定

這也說明分類的加載是不一樣的,我們可以把他們分為四類:

  1. 懶加載分類 & 懶加載類
    類的加載在第一次消息發(fā)送的時(shí)候,而分類的加載則在編譯時(shí)
消息發(fā)送的時(shí)候 -> lookuporforward ->  realizeClassWithoutSwift -> methodlizeClass 
不進(jìn)addUnattachedCategoryForClass,直接走data()
  1. 懶加載分類 & 非懶加載類
    類的加載在 _read_images 處,分類的加載還是在編譯時(shí)
read_images -> realizeClassWithoutSwift -> methodlizeClass -> 不需要添加表 -> 直接在相應(yīng)data() -> ro
  1. 非懶加載分類 & 懶加載類
    類的加載在 load_images 內(nèi)部,分類的加載在類加載之后的 methodizeClass
3.1 發(fā)送消息的時(shí)候就去讀取 -> realizeClassWithoutSwift -> methodlizeClass
3.2 就是我的類要在消息發(fā)送的時(shí)候才有 - 但是我的分類提前了 - 需要加載 - read_images - addUnattachedCategoryForClass - 但是沒有實(shí)現(xiàn)類 就會在下面 prepare_load_methods 實(shí)現(xiàn)
3.3 prepare_load_methods - realizeClassWithoutSwift 給你提前了實(shí)現(xiàn)類的信息 - unattachedCategoriesForClass
  1. 非懶加載分類 & 非懶加載類
    類的加載在 _read_images 處,分類的加載在類加載之后的 reMethodizeClass
read_images -> realizeClassWithoutSwift -> methodlizeClass -> addUnattachedCategoryForClass -> 判斷是否實(shí)現(xiàn) -> 這里看到上面一行就在read_images 實(shí)現(xiàn)了
if (cls->isRealized()) {
   remethodizeClass(cls); -> 實(shí)現(xiàn)類信息
}
attachCategories 加載分類數(shù)據(jù)進(jìn)來
參考資料

sunnyxx:objc_category_secret
美團(tuán):category

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

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

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