Xcode 11.6
express
libobjc:781
數組
在類的加載上中羅列了一些和類加載相關的方法以便於這一次的展開分析。主要相關方法以下:緩存
realizeClassWithoutSwift
ro
,建立rw
,賦值rw->ro
等等ro
中的一些標識到rw
中-> methodizeClass
cls
本身實現的方法和屬性,不包含分類 -> prepareMethodLists
ro
中的方法列表進行排序initialize
的實現sel
地址的排序,確保後續方法查找的數組是有序數組load_categories_nolock
attachCategories
/unattachedCategories.addForClass
class_rwe_ex_t
結構體 ->class_rw_t::extAlloc()
prepareMethodLists
),添加分類中的方法、協議、屬性列表到rwe
中 -> attachLists
class_rwe_ex_t
,複製ro
中的方法、協議以及屬性等數據到rwe
中。lists
中,具體操做爲開闢新的內存空間,採用頭插法,確保新插入元素位於二維數組的首位。在上述方法中,realizeClassWithoutSwift
爲實現類的必須方法,attachCategories
爲添加分類到主類的核心方法,經過在這兩個方法中打下斷點來觀測不一樣狀況下類和分類的加載狀況。markdown
懶加載指的是沒有實現+load方法,不然爲非懶加載app
...
@implementation ZHYPerson
- (void)method {}
- (void)instanceMethod {}
@end
...
@implementation ZHYPerson (Category1)
- (void)method {}
- (void)categoryMethod{}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZHYPerson *person = [ZHYPerson alloc];
複製代碼
觀察斷點發現咱們自定義的懶加載類
ZHYPerson
的實現是在main
函數執行實例化ZHYPerson
的時候觸發的。那麼分類是何時加載的呢?咱們先來打印一下當前cls
的方法列表。函數
(lldb) p ro->baseMethods()
(method_list_t *) $0 = 0x00000001000042c8
(lldb) p *$0
(method_list_t) $1 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 4
first = {
name = "method"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003190 (objc-debug`-[ZHYPerson(Category1) method] at main.m:91)
}
}
}
(lldb) p $1.get(1)
(method_t) $2 = {
name = "categoryMethod"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x00000001000031a0 (objc-debug`-[ZHYPerson(Category1) categoryMethod] at main.m:92)
}
(lldb) p $1.get(2)
(method_t) $3 = {
name = "method"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003170 (objc-debug`-[ZHYPerson method] at main.m:84)
}
(lldb) p $1.get(3)
(method_t) $4 = {
name = "instanceMethod"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003180 (objc-debug`-[ZHYPerson instanceMethod] at main.m:85)
}
複製代碼
此時ZHYPerson
的方法列表中一共有4個方法,他們的順序爲:post
1. [ZHYPerson(Category1) method]
2. [ZHYPerson(Category1) categoryMethod]
3. [ZHYPerson method]
4. [ZHYPerson instanceMethod]
複製代碼
此時就會產生三個疑問:優化
realizeClassWithoutSwift
的調用時機是在執行咱們本身的代碼ZHYPerson *person = [ZHYPerson alloc]
時,和上一節的分析好像不太一致。auto ro = (const class_ro_t *)cls->data()
獲得的,爲何會這樣呢?attachCategories
並無執行啊?1. 第一個疑問realizeClassWithoutSwift
的調用時機 上圖中按照從下往上有對應的方法調用註釋。由於分類和主類都是懶加載類,所以只有在須要用到的時候纔會實現,並無遵循非懶加載類的那一套流程。ui
2. 第二個疑問ro的獲取方式spa
class_rw_t *data() const {
return bits.data();
}
複製代碼
根據上面的方法實現得知cls->data()
方法原本應該獲得的是class_rw_t
,可是系統將rw
強轉成了ro
。另一點是此時咱們的類尚未實現,所以讀取到的數據應該是直接從macho文件中讀取到的,就是說是編譯期就肯定的數據。那麼惟一能夠解釋的就是咱們的類在編譯時並無class_rw_t這個結構,而是將class_ro_t這個編譯器肯定的結構體指針放在了rw指針的位置。
3. 第三個疑問分類是何時加載的? 通過第二個疑問的解答得出此時的ro是編譯期肯定的內容,同時咱們打印方法列表的時候發現分類中的方法method
和categoryMethod
都已經被添加到了方法列表中。這就是說分類已經在編譯期就加載到了咱們的主類當中。
此時咱們過掉斷點,發現attachCategories
方法始終沒有執行。
綜上能夠得出以下結論: 懶加載分類在編譯期就加載到了主類當中。
主類和分類都實現了load方法。
@interface ZHYPerson:NSObject
@end
@implementation ZHYPerson
+ (void)load {}
- (void)method {}
- (void)instanceMethod {}
@end
@interface ZHYPerson (Category1)
@end
@implementation ZHYPerson (Category1)
+ (void)load {}
- (void)method {}
- (void)categoryMethod{}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZHYPerson *person = [ZHYPerson alloc];
}
return 0;
}
複製代碼
觀察斷點發現此時類的實現是在
dyld
的流程中觸發的。此時咱們的main
函數尚未執行。這個結果符合懶加載類的特徵,咱們來打印一下ro
中的內容。
(lldb) p ro->baseMethods()
(method_list_t *) $0 = 0x0000000100004390
(lldb) p *$0
(method_list_t) $1 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 2
first = {
name = "method"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003160 (objc-debug`-[ZHYPerson method] at main.m:85)
}
}
}
(lldb) p $1.get(1)
(method_t) $2 = {
name = "instanceMethod"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003170 (objc-debug`-[ZHYPerson instanceMethod] at main.m:86)
}
複製代碼
發現ro
中只有兩個主類中的方法,說明當前的這種組合狀況下編譯期只肯定了主類的信息,分類並無加載進來。 過掉斷點:
attachCategories
執行了。此時的流程也是有dyld
觸發的,處於main
函數以前,打印ro的內容:
(lldb) p ro->baseMethods()
(method_list_t *) $0 = 0x0000000100004390
(lldb) p *$0
(method_list_t) $1 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 2
first = {
name = "method"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003160 (objc-debug`-[ZHYPerson method] at main.m:85)
}
}
}
(lldb) p $1.get(1)
(method_t) $2 = {
name = "instanceMethod"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003170 (objc-debug`-[ZHYPerson instanceMethod] at main.m:86)
}
複製代碼
ro的內容只是順序發生了變化,這是由於在realizeClassWithoutSwift
過程當中對方法進行了排序。接下來打印rwe中的methods:
(lldb) p rwe->methods
(method_array_t) $48 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x0000000100707f01
arrayAndFlag = 4302339841
}
}
}
(lldb) p $48.beginLists()
// 二維數組
(method_list_t *const *) $49 = 0x0000000100707f08
(lldb) p *$49
// 讀取數組的第一個元素method_list_t *指針
(method_list_t *const) $50 = 0x00000001000043c8
(lldb) p *$50
(method_list_t) $51 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "categoryMethod"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x00000001000031a0 (objc-debug`-[ZHYPerson(Category1) categoryMethod] at main.m:94)
}
}
}
(lldb) p $51.get(1)
(method_t) $52 = {
name = "method"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003190 (objc-debug`-[ZHYPerson(Category1) method] at main.m:93)
}
(lldb) p *($49+1)
// 讀取數組的第一個元素method_list_t *指針
(method_list_t *const) $53 = 0x0000000100004390
(lldb) p *$53
(method_list_t) $54 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "instanceMethod"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003170 (objc-debug`-[ZHYPerson instanceMethod] at main.m:86)
}
}
}
(lldb) p $54.get(1)
(method_t) $55 = {
name = "method"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003160 (objc-debug`-[ZHYPerson method] at main.m:85)
}
複製代碼
咱們知道編譯期類的結構中並無rwe
,是在運行時類的結構須要發生變化時纔會生成,生成以後會將ro中對應的數據拷貝到rwe中,同時還負責運行時添加的一些其餘數據。經過上述打印咱們能夠驗證一下幾點:
rwe
中的methods
是一個存儲method_list_t
指針的數組,對應的是ro
中的方法列表或者分類
中的方法列表主類實現load方法,分類不實現
@interface ZHYPerson:NSObject
@end
@implementation ZHYPerson
+ (void)load {}
- (void)method {}
- (void)instanceMethod {}
@end
@interface ZHYPerson (Category1)
@end
@implementation ZHYPerson (Category1)
- (void)method {}
- (void)categoryMethod{}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZHYPerson *person = [ZHYPerson alloc];
}
return 0;
}
複製代碼
類的實現是在dyld流程中執行,在main函數以前,打印ro:
(lldb) p ro.baseMethods()
(method_list_t *) $0 = 0x00000001000042d0
Fix-it applied, fixed expression was:
ro->baseMethods()
(lldb) p *$0
(method_list_t) $1 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 4
first = {
name = "method"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003190 (objc-debug`-[ZHYPerson(Category1) method] at main.m:92)
}
}
}
(lldb) p $1.get(1)
(method_t) $2 = {
name = "categoryMethod"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x00000001000031a0 (objc-debug`-[ZHYPerson(Category1) categoryMethod] at main.m:93)
}
(lldb) p $1.get(2)
(method_t) $3 = {
name = "method"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003170 (objc-debug`-[ZHYPerson method] at main.m:85)
}
(lldb) p $1.get(3)
(method_t) $4 = {
name = "instanceMethod"
types = 0x0000000100003e08 "v16@0:8"
imp = 0x0000000100003180 (objc-debug`-[ZHYPerson instanceMethod] at main.m:86)
}
複製代碼
分類的方法在編譯期已經加入到了ro
中,因爲主類是非懶加載的緣由,在main函數以前就實現了。
主類不實現load方法,分類實現load方法。
@interface ZHYPerson:NSObject
@end
@implementation ZHYPerson
- (void)method {}
- (void)instanceMethod {}
@end
@interface ZHYPerson (Category1)
@end
@implementation ZHYPerson (Category1)
+ (void)load {}
- (void)method {}
- (void)categoryMethod{}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZHYPerson *person = [ZHYPerson alloc];
}
return 0;
}
複製代碼
當主類是懶加載,分類是非懶加載時
realizeClassWithoutSwift
的調用堆棧如上圖所示,此時觸發時機延遲到了load_images
方法中。咱們先來看一下主類中ro
的數據:
(lldb) p ro->baseMethods()
(method_list_t *) $0 = 0x0000000100002200
(lldb) p *$0
(method_list_t) $1 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 24
count = 2
first = {
name = "method"
types = 0x0000000100001ecd "v16@0:8"
imp = 0x0000000100001650 (objc-debug`-[ZHYPerson method] at main.m:84)
}
}
}
(lldb) p $1.get(1)
(method_t) $2 = {
name = "instanceMethod"
types = 0x0000000100001ecd "v16@0:8"
imp = 0x0000000100001660 (objc-debug`-[ZHYPerson instanceMethod] at main.m:85)
}
複製代碼
此時主類的ro的方法列表中只有兩個方法:
說明當前狀況下分類的數據在編譯期並無合併到主類中。跳過當前斷點來到attachCategories
中: 此時分類數據已經加載完畢,咱們來看一下rwe中的數據狀況:
(lldb) p rwe->methods
(method_array_t) $3 = {
list_array_tt<method_t, method_list_t> = {
= {
list = 0x000000010105c6a1
arrayAndFlag = 4312123041
}
}
}
(lldb) p $3.beginLists()
(method_list_t *const *) $4 = 0x000000010105c6a8
(lldb) p *$4
(method_list_t *const) $5 = 0x0000000100002238
(lldb) p *$5
(method_list_t) $6 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "categoryMethod"
types = 0x0000000100001ecd "v16@0:8"
imp = 0x0000000100001690 (objc-debug`-[ZHYPerson(Category1) categoryMethod] at main.m:93)
}
}
}
(lldb) p $6.get(1)
(method_t) $7 = {
name = "method"
types = 0x0000000100001ecd "v16@0:8"
imp = 0x0000000100001680 (objc-debug`-[ZHYPerson(Category1) method] at main.m:92)
}
(lldb) p *($4+1)
(method_list_t *const) $8 = 0x0000000100002200
(lldb) p *$8
(method_list_t) $9 = {
entsize_list_tt<method_t, method_list_t, 3> = {
entsizeAndFlags = 26
count = 2
first = {
name = "instanceMethod"
types = 0x0000000100001ecd "v16@0:8"
imp = 0x0000000100001660 (objc-debug`-[ZHYPerson instanceMethod] at main.m:85)
}
}
}
(lldb) p $8.get(1)
(method_t) $10 = {
name = "method"
types = 0x0000000100001ecd "v16@0:8"
imp = 0x0000000100001650 (objc-debug`-[ZHYPerson method] at main.m:84)
}
複製代碼
能夠看到rwe的方法列表中數組中放着分類的方法列表和主類的方法列表,且分類在前,主類在後,此時的結構和主類分類同爲非懶加載類的結構是同樣的。 主要緣由有兩個:
1. 當前cls是懶加載類,所以在map_images
中處理非懶加載類時並無執行cls的實現。
2. 當前cls的分類是非懶加載,所以在load_images
中觸發了非懶加載分類的加載,因爲此時主類尚未實現,分類被暫時保存到了unattachedCategories
中,在prepare_load_methods
中處理非懶加載分類時,觸發了對主類的加載,加載過程當中將unattachedCategories
中保存的對應分類添加到了主類中
動態添加方法通常使用class_addMethod
,具體實現以下:
BOOL
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;
mutex_locker_t lock(runtimeLock);
return ! addMethod(cls, name, imp, types ?: "", NO);
}
複製代碼
addMethod
static IMP
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
IMP result = nil;
runtimeLock.assertLocked();
checkIsKnownClass(cls);
ASSERT(types);
ASSERT(cls->isRealized());
method_t *m;
// 從當前類中查找SEL對應的方法
if ((m = getMethodNoSuper_nolock(cls, name))) {
// already exists
if (!replace) {
// 不能替換的話會返回該方法的IMP
result = m->imp;
} else {
// 能夠替換的話就替換IMP
result = _method_setImplementation(cls, m, imp);
}
} else {
// 並無在當前類中找到SEL對應的方法,此時須要查詢rwe是否存在,若是沒有就建立rwe
auto rwe = cls->data()->extAllocIfNeeded();
// fixme optimize
// 生成一個method_list_t,將方法對應的數據放進去
method_list_t *newlist;
newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(method_t) | fixed_up_method_list;
newlist->count = 1;
newlist->first.name = name;
newlist->first.types = strdupIfMutable(types);
newlist->first.imp = imp;
prepareMethodLists(cls, &newlist, 1, NO, NO);
// 添加方法列表到rwe的methods中
rwe->methods.attachLists(&newlist, 1);
// 清空緩存
flushCaches(cls);
result = nil;
}
return result;
}
複製代碼
主要流程以下:
1. 在當前類cls中查找是否存在和sel重名的方法
2. 若是找到直接返回對應的imp(replace參數爲NO,說明不能替換,不然直接替換imp)
3. 沒有找到sel對應的method
4. 取出rwe或建立rwe
5. 初始化一個method_list_t數組,將須要添加的方法sel、簽名、實現等賦值給第一個元素
6. 將數組添加到rwe的methods中
7. 清空緩存,此時方法列表已經發生了變化
類懶加載和分類非懶加載的狀況在Xcode12
表現有些異樣,多是我寫錯了,此處存疑。