OC底層原理十七:類的加載(上) read_images & 懶加載類

OC底層原理 學(xué)習(xí)大綱

上一節(jié)伦吠,我們了解了dyld和objc的關(guān)聯(lián)循榆,但是 map_images是如何將鏡像macho映射內(nèi)存的呢咖驮?

dyld與objc關(guān)聯(lián)

本節(jié)內(nèi)容:

  1. _read_images結(jié)構(gòu)分析
  2. 類的加載(上)
    2.1 readClass 讀取類
    2.2 realizeClassWithoutSwift 實現(xiàn)類
    2.3 methodizeClass 整理類
  3. 懶加載非懶加載的區(qū)別

準(zhǔn)備工作:


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讀取鏡像:
image.png

以下是_read_images源碼結(jié)構(gòu):

image.png
  1. 條件控制進(jìn)行一次的加載
  2. 修復(fù)預(yù)編譯階段的@selector的混亂問題
  3. 類處理
  4. 修復(fù)重映射一些沒被鏡像文件加載進(jìn)來的類
  5. 修復(fù)一些消息
  6. 當(dāng)類中有協(xié)議時:readProtocol
  7. 修復(fù)沒被加載的協(xié)議
  8. 分類處理
  9. 實現(xiàn)非懶加載類
  10. 沒被處理的類(優(yōu)化哪些被侵犯的類)

這里內(nèi)容較多侨颈,我們先抓重點:macho讀取到內(nèi)存雀久,最重要的是類信息讀取

  • 我們發(fā)現(xiàn)首先對進(jìn)行操作的是第3步 類處理寥裂。主要函數(shù)是readClass類的讀取:
image.png

我們進(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行加入斷點)鳖昌,運行代碼:
image.png
  • 代碼進(jìn)入斷點备畦,確認(rèn)是HTPerson類后,我們加斷點運行(上圖3227遗遵、3241行)萍恕,發(fā)現(xiàn)沒進(jìn)類的讀取和加載區(qū)域。
    我們單步執(zhí)行车要,查看具體流程:
image.png
  • 發(fā)現(xiàn)進(jìn)入了addNamedClass函數(shù)允粤。
image.png

參數(shù)mangledName 是當(dāng)前類名:

  • 已實現(xiàn)的類,從內(nèi)存讀纫硭辍类垫;未實現(xiàn)的類,從machO讀取
    image.png
  • 進(jìn)入addNamedClass函數(shù):
image.png
  • 發(fā)現(xiàn)主要是將類名插入類表中琅坡,NXMapInsert內(nèi)部有關(guān)于類名哈希悉患、表擴(kuò)容插入算法等細(xì)節(jié)介紹榆俺。此處不做拓展售躁。

  • 我們返回上一步,查看下一個addClassTableEntry函數(shù):

image.png

細(xì)節(jié):

    1. 此處會將本類元類注冊類表
    1. 當(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);
     }
image.png
image.png

然而,尷尬的是... 都沒有執(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)非懶加載類】

image.png
  • 進(jìn)入realizeClassWithoutSwift內(nèi)部:
realizeClassWithoutSwift

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 整理類

123.png

此時rweNull霹疫,所有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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末官地,一起剝皮案震驚了整個濱河市酿傍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驱入,老刑警劉巖赤炒,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異亏较,居然都是意外死亡莺褒,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門雪情,熙熙樓的掌柜王于貴愁眉苦臉地迎上來遵岩,“玉大人,你說我怎么就攤上這事旺罢】跤啵” “怎么了绢记?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長正卧。 經(jīng)常有香客問我蠢熄,道長,這世上最難降的妖魔是什么炉旷? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任签孔,我火速辦了婚禮,結(jié)果婚禮上窘行,老公的妹妹穿的比我還像新娘饥追。我一直安慰自己,他們只是感情好罐盔,可當(dāng)我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布但绕。 她就那樣靜靜地躺著,像睡著了一般惶看。 火紅的嫁衣襯著肌膚如雪捏顺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天纬黎,我揣著相機(jī)與錄音幅骄,去河邊找鬼。 笑死本今,一個胖子當(dāng)著我的面吹牛拆座,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播冠息,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼挪凑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了铐达?” 一聲冷哼從身側(cè)響起岖赋,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤檬果,失蹤者是張志新(化名)和其女友劉穎瓮孙,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體选脊,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡杭抠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了恳啥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片偏灿。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖钝的,靈堂內(nèi)的尸體忽然破棺而出翁垂,到底是詐尸還是另有隱情铆遭,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布沿猜,位于F島的核電站枚荣,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏啼肩。R本人自食惡果不足惜橄妆,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望祈坠。 院中可真熱鬧害碾,春花似錦、人聲如沸赦拘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽躺同。三九已至儒陨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笋籽,已是汗流浹背蹦漠。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留车海,地道東北人笛园。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像侍芝,于是被迫代替她去往敵國和親研铆。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,914評論 2 355