一、類的加載
上一篇我們有分析非懶加載類的加載過程,接下來我們可以在_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方法

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

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

打斷點(diǎn)然后跟進(jìn)去,最終我們會發(fā)現(xiàn)一個(gè)我們上一篇分析過的方法
realizeClassWithoutSwift,這里會進(jìn)行ro/rw、category的操作。這時(shí)候打印
inst(即Dog對象),可以看到rw有值了,說明類已經(jīng)被加載。
1.2 總結(jié)
下面總結(jié)一下懶加載類加載的流程:
- 類第一次加載的時(shí)候沒有緩存,所以會來到
_class_lookupMethodAndLoadCache3->lookUpImpOrForward -
lookUpImpOrForward會做一次[圖片上傳中...(懶加載類.png-c5360e-1593754564326-0)]
判斷,如果沒有實(shí)現(xiàn)(!cls->isRealized()),會來到realizeClassWithoutSwift,也就是最終實(shí)現(xiàn)類加載的地方。
懶加載類和非懶加載類的調(diào)用堆棧如下:


二、分類的加載
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。

通過斷點(diǎn),我們發(fā)現(xiàn)
cats為NULL,即unattachedCategoriesForClass并沒有獲取到分類。但是我們打印ro和rw,發(fā)現(xiàn)我們methods是有內(nèi)容的(sayByebye是我們手動添加到分類的類方法)。也就是說,如果是懶加載類,并且分類也是懶加載,那么分類在
編譯時(shí)直接加載到了類的 ro 里面,然后在運(yùn)行時(shí)被拷貝到了類的 rw 里面。
3.2 總結(jié)
分類的加載可以簡單以是否實(shí)現(xiàn)load方法分類:
-
懶加載:編譯時(shí)確定 -
非懶加載:運(yùn)行時(shí)確定
這也說明分類和類的加載是不一樣的,我們可以把他們分為四類:
-
懶加載分類 & 懶加載類:
類的加載在第一次消息發(fā)送的時(shí)候,而分類的加載則在編譯時(shí)
消息發(fā)送的時(shí)候 -> lookuporforward -> realizeClassWithoutSwift -> methodlizeClass
不進(jìn)addUnattachedCategoryForClass,直接走data()
-
懶加載分類 & 非懶加載類:
類的加載在 _read_images 處,分類的加載還是在編譯時(shí)
read_images -> realizeClassWithoutSwift -> methodlizeClass -> 不需要添加表 -> 直接在相應(yīng)data() -> ro
-
非懶加載分類 & 懶加載類:
類的加載在 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
-
非懶加載分類 & 非懶加載類:
類的加載在 _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)來