上一節(jié)伦吠,我們了解了dyld和objc的關(guān)聯(lián)循榆,但是 map_images
是如何將鏡像
從macho
中映射
到內(nèi)存
的呢咖驮?
本節(jié)內(nèi)容:
-
_read_images
結(jié)構(gòu)分析 - 類的加載(上)
2.1readClass
讀取類
2.2realizeClassWithoutSwift
實現(xiàn)類
2.3methodizeClass
整理類 -
懶加載
和非懶加載
的區(qū)別
準(zhǔn)備工作:
- 可編譯的
objc4-781
源碼: http://www.reibang.com/p/45dc31d91000dyld-750.6
: https://opensource.apple.com/tarballs/dyld/
1. _read_images
結(jié)構(gòu)分析
打開objc4
源碼沮稚,找到_objc_init
, 進(jìn)入map_images
:
void _objc_init(void)
{
... ...
// 注冊
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
// ?? 進(jìn)入map_images
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}
- 進(jìn)入
map_images_nolock
,找到核心代碼_read_images
讀取鏡像:
以下是_read_images
源碼結(jié)構(gòu):
- 條件控制進(jìn)行一次的加載
- 修復(fù)預(yù)編譯階段的@selector的混亂問題
- 類處理
- 修復(fù)重映射一些沒被鏡像文件加載進(jìn)來的類
- 修復(fù)一些消息
- 當(dāng)類中有協(xié)議時:readProtocol
- 修復(fù)沒被加載的協(xié)議
- 分類處理
- 實現(xiàn)非懶加載類
- 沒被處理的類(優(yōu)化哪些被侵犯的類)
這里內(nèi)容較多侨颈,我們先抓重點:macho
讀取到內(nèi)存
雀久,最重要的是類信息
的讀取
。
- 我們發(fā)現(xiàn)首先對
類
進(jìn)行操作
的是第3步 類處理
寥裂。主要函數(shù)是readClass
類的讀取:
我們進(jìn)入readClass
嵌洼,詳細(xì)了解內(nèi)部功能??
2. 類的加載(上)
- 先加上
測試代碼
,使用自定義HTPerson
類封恰,便于定位
和驗證
:
@interface HTPerson : NSObject
@property (nonatomic, copy) NSString *ht_name;
@property (nonatomic, assign) int ht_age;
- (void)ht_func1;
- (void)ht_func3;
- (void)ht_func2;
+ (void)ht_classFunc;
@end
@implementation HTPerson
- (void)ht_func1 { NSLog(@"%s",__func__); };
- (void)ht_func3 { NSLog(@"%s",__func__); };
- (void)ht_func2 { NSLog(@"%s",__func__); };
+ (void)ht_classFunc { NSLog(@"%s",__func__); };
@end
2.1 readClass 讀取類
小技巧:
- 源碼中
加入
類的判斷語句
麻养,精準(zhǔn)定位
來分析自己的類
。
- 在
readClass
函數(shù)內(nèi)加入識別
HTPerson的測試代碼
诺舔,在測試代碼
區(qū)域加入斷點
(下圖僅在3196行加入斷點)鳖昌,運行代碼:
- 代碼進(jìn)入斷點备畦,確認(rèn)是
HTPerson
類后,我們加斷點運行
(上圖3227遗遵、3241行)萍恕,發(fā)現(xiàn)沒進(jìn)
入類的讀取和加載
區(qū)域。
我們單步執(zhí)行车要,查看具體流程:
- 發(fā)現(xiàn)進(jìn)入了
addNamedClass
函數(shù)允粤。
參數(shù)
mangledName
是當(dāng)前類名:
已實現(xiàn)
的類,從內(nèi)存
讀纫硭辍类垫;未實現(xiàn)
的類,從machO
讀取
image.png
- 進(jìn)入
addNamedClass
函數(shù):
發(fā)現(xiàn)主要是將
類名
插入類表
中琅坡,NXMapInsert
內(nèi)部有關(guān)于類名哈希
悉患、表擴(kuò)容
、插入算法
等細(xì)節(jié)介紹榆俺。此處不做拓展售躁。我們返回上一步,查看下一個
addClassTableEntry
函數(shù):
細(xì)節(jié):
- 此處會將
本類
和元類
都注冊
到類表
中
- 當(dāng)前類表中的本類和元類茴晋,都只有
名字
陪捷、地址
,數(shù)據(jù)還沒寫入
- 我們發(fā)現(xiàn)
第3步 類處理 readClass
內(nèi)部并沒有完成類的加載
,僅僅將類名
和類地址
記錄到類表中诺擅。
返回_read_images
中市袖,繼續(xù)尋找與類相關(guān)
的步驟
,發(fā)現(xiàn)第9步 實現(xiàn)非懶加載類
和第10步 沒被處理的類(優(yōu)化哪些被侵犯的類)
都調(diào)用了realizeClassWithoutSwift
函數(shù)烁涌,對類進(jìn)行實現(xiàn)苍碟。
2.2 realizeClassWithoutSwift 實現(xiàn)類
我們在_read_images
函數(shù)的第9步
和第10步
分別加上測試代碼:
const char * mangledName = cls->mangledName();
const char * HTPersonName = "HTPerson";
if (strcmp(mangledName, HTPersonName) == 0) {
printf("resolvedFutureClasses: 精準(zhǔn)定位 %s \n", HTPersonName);
}
然而,尷尬的是... 都沒有執(zhí)行到 ??
(倔強(qiáng)的我撮执,HTPerson類
進(jìn)入_read_images
后微峰,單步斷點
一步步的測試,確實發(fā)現(xiàn)HTPerson類
沒有實現(xiàn))
思考: 此處是
app啟動
前抒钱,dyld
調(diào)用_objc_init
县忌,執(zhí)行map_images
,發(fā)現(xiàn)自定義的HTPerson
類沒有加載
- 如果你在開發(fā)中用過
lazy懶加載
继效,就應(yīng)該能聯(lián)想
到蘋果的設(shè)計機(jī)制
症杏。沒錯,此處也是懶加載模式
瑞信。- 當(dāng)類
沒被調(diào)用
時厉颤,我們只會存儲類名
、類地址
- 真正
被調(diào)用
時凡简,會檢查是否實現(xiàn)逼友,未實現(xiàn)就會觸發(fā)實現(xiàn)
精肃。
是否是懶加載類
,關(guān)鍵在于是否實現(xiàn)
了+load方法
帜乞。 (沒實現(xiàn)
+load方法是懶加載類
司抱,實現(xiàn)了
就是非懶加載類
)
- 我們在
HTperson類
的測試代碼
中加入+load
的實現(xiàn):
+ (void)load { NSLog(@"%s",__func__); };
再次運行代碼
,按照上述流程黎烈,發(fā)現(xiàn)精準(zhǔn)定位
到了【9.實現(xiàn)非懶加載類】
:
- 進(jìn)入
realizeClassWithoutSwift
內(nèi)部:
Q:賦值后bits為何為空习柠?
image.png
所以此刻賦值已成功
,但是還未寫入內(nèi)存
ro的讀取 :
image.png
WWDC2020視頻
Advancements in the Objective-C runtime有介紹照棋,據(jù)蘋果官方統(tǒng)計
资溃,只有10%
的類需要動態(tài)修改方法
,所以為了節(jié)約內(nèi)存
烈炭,沒進(jìn)行動態(tài)拓展的類溶锭,直接從macho
讀取數(shù)據(jù)
。反之符隙,用rwe
記錄動態(tài)修改的數(shù)據(jù)趴捅,并從rwe
讀取數(shù)據(jù)。
下面我們進(jìn)一步探索methodizeClass
整理類
2.3 methodizeClass 整理類
此時rwe
為Null
霹疫,所有rwe
條件判斷后的賦值
拱绑,都沒進(jìn)入
。
Q:
rwe
什么時候會被賦值
更米?根據(jù)
WWDC2020視頻
Advancements in the Objective-C runtime欺栗,我們知道rwe
是當(dāng)類的方法
被動態(tài)修改
時毫痕,才會創(chuàng)建
征峦。 那什么時候會被動態(tài)修改
呢?我們下一節(jié)會分析消请。
在分析rwe
的創(chuàng)建之前栏笆,我們先詳細(xì)講解懶加載類
和非懶加載類
的區(qū)別
3. 懶加載和非懶加載的區(qū)別
懶加載類
和非懶加載類
的區(qū)別
在于:是否實現(xiàn)了+load
方法。
上面我們加入了
+load
方法臊泰,讓HTPerson類
變成了非懶加載類
蛉加,才進(jìn)入了realizeClassWithoutSwift
函數(shù)。那如果不實現(xiàn)
+load
方法缸逃,是懶加載類
针饥,又是如何加載到內(nèi)存的呢?
懶加載類
的加載:
- 測試代碼中需频,
移除+load方法
的實現(xiàn)丁眼,加入HTPerosn
的調(diào)用
:int main(int argc, const char * argv[]) { @autoreleasepool { HTPerson * person = [HTPerson alloc]; // 加入HTPerson的調(diào)用 } return 0; }
移除
所有斷點
,在realizeClassWithoutSwift
函數(shù)中昭殉,加入定位代碼
和斷點
:const char * HTPersonName = "HTPerson"; const char * mangledName = cls->mangledName(); if(strcmp(mangledName, HTPersonName) == 0) { auto ht_ro = (const class_ro_t *)cls->data(); auto ht_isMeta = ht_ro->flags & RO_META; if (!ht_isMeta) { printf("%s - 精準(zhǔn)定位! - %s\n", __func__, mangledName); } }
- 運行程序苞七,
斷點
精準(zhǔn)定位到HTPerson類
藐守,查看左邊堆棧信息
:image.png
這個流程是否非常熟悉? ?? 這就是之前我們詳細(xì)分析過的objc_msgSend流程蹂风,
APP啟動后卢厂,我們手動調(diào)用了
alloc
方法,觸發(fā)消息機(jī)制
惠啄,在進(jìn)入方法的慢速查找
時慎恒,我們會現(xiàn)檢測當(dāng)前類是否已實現(xiàn)
,如果沒有實現(xiàn)
礁阁,就調(diào)用realizeClassWithoutSwift
進(jìn)行實現(xiàn)巧号。image.png
懶加載類
與非懶加載類
總結(jié):
- 蘋果系統(tǒng)
默認(rèn)
所有類都是懶加載類
,這樣不占用啟動時間
姥闭,且不占用過多資源
丹鸿。
image.png
-
本節(jié)回顧:
image.png
本節(jié)我們了解了map_images
如何將鏡像
從macho
映射到內(nèi)存
中,分析類的加載
棚品,了解懶加載類
與非懶加載類
的區(qū)別靠欢。
- 但是,關(guān)于
rwe
何時加載铜跑?分類
的加載方式等门怪,很多細(xì)節(jié)我們都沒有深入探索。
下一節(jié)锅纺,OC底層原理十八:類的加載(中) SEL & 分類的加載 我們將從分類
的探索開始掷空,深入了解整個流程。
附錄:
Q: 我們知道+load方法
在main函數(shù)之前
執(zhí)行囤锉,那本類
和分類
的+load執(zhí)行順序
坦弟,不相干
的兩個類
的執(zhí)行順序
是怎樣的呢?
A:給大家分享這位博主的分析:https://blog.csdn.net/TuGeLe/article/details/86599216