在上一篇文章類的加載(上)了解了到了_read_images大概流程咽块,也詳細講解了readClass方法 技能回顧
下面講解 標紅區(qū)域 因為講的是類的加載,當類的加載了解清楚了 其他步驟 也就清晰可見
懶加載類和非懶加載類
首先我們看類加載處理這里的源碼
在源碼注釋里我們很清晰的看到了一句話 Realize non-lazy classes (for +load methods and static instances) 翻譯過來 就是 實現(xiàn)非懶加載類(當實現(xiàn)了+load方法 或者 static instances)?
帶著疑問 我們修改源碼 在 類的加載(上)我們曾在readClass里面也進行了修改 這次我們在 下面繼續(xù)斷點 并 修改源碼目的針對性研究
非懶加載類環(huán)境流程
將LGPerson里的項目 添加上+load 運行 按照源碼所說 此時它為非懶加載類
項目斷在readClass 并繼續(xù)過斷點
我們看到確實來到了修改源碼的斷點 并即將進入 realizeClassWithoutSwift 函數(shù) 下面我們進行研究
realizeClassWithoutSwift
源碼注釋此方法的含義
解讀
- 首次對類cls進行初始化,
- 包括分配讀寫數(shù)據(jù)芳来。
- 不執(zhí)行任何快速端初始化构回。
- 返回類的真實類結構驰凛。
源碼查看及解讀 (由于代碼太長分段解讀)
第一步:讀取data數(shù)據(jù)
- 當前類的判空 是否已經(jīng)實現(xiàn) 的判斷
- 未實現(xiàn)也不為nil 讀取 class的data數(shù)據(jù) 并強轉為 (class_ro_t*)類型 獲取 ro
- 獲取元類判斷條件
- 如果是未來的類 剪芥,此時 rw 數(shù)據(jù) 已經(jīng)準備好 直接讀取 data() 獲取 rw ro
- 正常的類 分配可寫入的類數(shù)據(jù)。
- 申請開辟 <class_rw_t>數(shù)據(jù)結構 rw模板
- 根據(jù)ro設置rw 從ro中copy到rw中
- 將cls的data賦值為rw形式
第二步:遞歸調用 realizeClassWithoutSwift 完善 繼承鏈
-
遞歸調用 realizeClassWithoutSwift設置父類剃幌、元類
2.如果類已經(jīng)實現(xiàn)抖棘,則直接返回cls
注: isa找到根元類之后聋涨,根元類的isa是指向自己,并不會返回nil负乡,所以有以下遞歸終止條件牍白,其目的是保證類只加載一次
1.如果類不存在,則返回nil
-
remapClass 如cls不存在茂腥,則返回nil
-
remapClass 如cls不存在茂腥,則返回nil
設置父類和元類的isa指向
通過addSubclass 和 addRootClass設置父子的雙向鏈表指向關系,即父類中可以找到子類切省,子類中可以找到父類
第三步:通過 methodizeClass 方法化類
源碼注釋此方法的含義
- 修復cls的方法列表最岗、協(xié)議列表和屬性列表。
- 附加任何突出類別朝捆。
源碼查看及解讀
- 在realizeClassWithoutSwift 我們已經(jīng)拿到了 ro rw 沒見到 rwe的蹤影
- method_list 方法處理
注:在這里 我們想起了在 消息發(fā)送流程的慢速查找流程般渡,其中查找算法是二分查找
,說明sel-imp是有序排列,那么如何排序的呢驯用?
就是在這里
它內部是通過fixupMethodList
方法排序
進入fixupMethodList
源碼實現(xiàn)脸秽,是根據(jù)selector address排序
- 無論是
methods
還是properties
還是protocols
我么發(fā)現(xiàn)都去調用了一個方法attachLists
添加上針對的代碼 并斷點到LGPerson
結果發(fā)現(xiàn) rwe 為NULL 并未運行其 rwe為真的判斷 為什么 留疑問
-
繼續(xù)過斷點來到了 attachToClass
在methodlist方法主要是將分類添加到主類中 當前環(huán)境沒有分類所以下面都沒有運行
懶加載類環(huán)境流程
??上面的流程我們都是在實現(xiàn)了load的情況下進行跟進的下面??我們來屏蔽load使其變?yōu)閼屑虞d環(huán)境
并在初始化LGPerson代碼斷言 運行
發(fā)現(xiàn) 在main函數(shù)之前調用的 mapimages -> _read_images -> readClass
在次向下走一步斷點
進入到methodizeClass里查看調用棧
這不正是我們之前學習的消息慢速轉發(fā)流程lookImpOrForward發(fā)起的調用嘛
總結
- 什么是懶加載類 和非懶加載類
- ro rw rwe 定義及為什么存在
app在使用類時,是需要在磁盤中app的二進制文件中讀取類的信息蝴乔,二進制文件中的類存儲了類的元類记餐、父類、flags和方法緩存 那么類的額外信息(name薇正、方法剥扣、協(xié)議和實例變量等)存儲在class_ro_t中
class_ro_t簡稱 ro: read only 干凈的內存空間 只讀 又稱為 clean memory
class_rw_t簡稱 rw: read write 意義: 由于ios有運行時 會不斷的插入 添加 刪除 也就是 增刪改 查 頻繁 對當前 這塊內存操作比較嚴重 為了防止對原始數(shù)據(jù)的破壞,所以就有了 rw 用于讀寫編寫程序铝穷。 drity memory 在進程運行時發(fā)生更改的內存。類一經(jīng)使用運行時就會分配一個額外的內存佳魔,那么這個內存變成了drity memory曙聂。但是在實際應用中,類的使用量只是10%鞠鲜,這樣就在rw中造成了內存浪費宁脊,所以蘋果就把rw中方法、協(xié)議和實例變量等放到了class_rw_ext_t中贤姆。
-
class_rw_ext_t簡稱 rwe: read write ext榆苞,用于運行時存儲類的方法、協(xié)議和實例變量等信息霞捡。
分類的加載流程
attachToClass
在methodlist方法主要是將分類添加到主類中坐漏,其源碼實現(xiàn)如下
- 找到一個分類進來一次,即一個一個加載分類碧信,不要混亂
- 當主類沒有實現(xiàn)load, 分類開始加載赊琳、迫使主類加載,會走到if流程
下面研究 attachCategories
attachCategories
源碼注釋解析
將方法列表砰碴、屬性和協(xié)議從類別附加到類中躏筏。
假設所有cats中的類別都已加載并按加載順序排序,
先有最古老的類別呈枉。-
只有少數(shù)類在啟動期間有超過64個類別趁尼。
這使用了一個小堆棧,并避免malloc猖辫。類別必須按正確的順序添加酥泞,也就是從后到前。為了完成分塊操作住册,我們從前向后迭代cats_list婶博,向后構建本地緩沖區(qū),并在塊上調用attachLists。attachLists突出顯示的
列表凡人,因此最終結果按照預期的順序名党。
在這里我們看到了 rwe 賦值
-
判斷rwe是否存在,存在直接取值不存在 則根據(jù)ro開辟挠轴。
點進去我們也看到了 attachLists方法 從0-1的過程也存在下面進行分析它
attachLists
經(jīng)過分析 我們可以看到 attachLists 方法存在三種情況 下面為 rwe 方法列表 0 -1的過程
回到上面斷點處 我們打印 rwe
此時沒有走下面的時候 rwe的 methods 正是 本類也就是ro->baseMethods 數(shù)據(jù)的 copy
我們繼續(xù)向下走
畫圖表示 這個算法
- 先存進之前開辟好的64個空間的棧里
- mlists + ATTACH_BUFSIZ - mcount 獲取mlists的首地址+ 64 - 有幾個分類 最終等于 這幾個分類的真實地址传睹。抹掉沒用到的。
- 排序
-
存入到rwe的methods中.
1). 從上面的0-1 過程 就是 開始創(chuàng)建 rwe 將本類 ro中的baseMethods存入rwe中 此時 attachlists 是 0-1 是一個 一緯數(shù)組岸晦。
2). 這次再進入 就是一個 1對多 的過程欧啤。
在下面是 多對多的邏輯示意圖
總結
- readClass 主要是讀取類,即此時的類僅有地址+名稱启上,還沒有data數(shù)據(jù)
- 懶加載類 未實現(xiàn)load的的類 數(shù)據(jù)加載推遲到第一次消息的時候
- 非懶加載類 map_images的時候 加載所有的類數(shù)據(jù)
- methodizeClass 兩種情況都會調用 一個是main函數(shù)之前 一個是消息發(fā)送之慢速查找邢隧。
- 在mehodizeClass里 方法序列化 為了 在慢速查找中的二分查找, 有附加分類 方法 冈在,主要是將 rwe的初始化(從ro中獲取主類初始值)倒慧、將主類的 methods、 properties包券、 protocols 等添加到 rwe中纫谅, 其帖進去的方法核心為 attachLists函數(shù)算法
類的加載源碼解析分析圖
上面我們已經(jīng)大概理解了類是如何從Mach-0 加載到內存中,下面我們來詳細了解 分類 如何加載到類中溅固,以及 類 + 分類搭配使用情況
分類的本質
通過 clang 以及 蘋果官方文檔 及 objc源碼 我們都可以了解到分類的本質就是一個結構體- instanceMethods: 實例方法列表
- classMethods: 類方法列表
- protocols: 協(xié)議表
- instanceProperties: 實例屬性 表
- _classProperties : 類屬性表 付秕; 拓: @property(class,nonatomic,copy) NSString * classPropertie
- methodsForMeta() : 獲取方法列表 判斷是否是元類 還是 類。(因為我們知道 實例方法在類中侍郭,類方法在元類中)
- propertiesForMeta() 獲取元類或者類的屬性 表
- protocolsForMeta() 獲取 元類 或者 類的 協(xié)議表 (從這里可以看出協(xié)議只存在類對象中)
分類的調用時機
上面的流程分析了 attachCategories的源碼干了點什么事情它主要是 是否存才 rwe 不存在 初始化rwe 及 將分類的信息 帖到 rwe中 下面是它的流程
realizeClassWithoutSwift -> methodizeClass -> attachToClass -> attachCategories
上面支線我們已經(jīng)跑通 下面來全局搜索 attachCategories 看哪里還用到了它經(jīng)過我們的反推有了以下流程
load_images -> loadAllCategories -> load_categories_nolock -> attachCategories
示意圖
realizeClassWithoutSwift 方法 (已知)
懶加載類的時候 是 由 lookUpImpOrForward 發(fā)起的調用
非懶加載類的時候 是由 map_images 發(fā)起的-
attachCategories 方法
- 首先必要條件是研究的這個類存在分類(已知)
- attachToClass 是由realizeClassWithoutSwift掉起的 什么情況下才會掉起 attachCategories ?
- 既然 有懶加載類 和 非懶加載 區(qū)別 那么 分類是不是也有询吴?
- 通過反推查看objc源碼 得知 load_images 也可以掉起 attachCategories?什么時機掉起的励幼?
帶著上面已知和未知的問題 就引出了下面要講解的 類 和 分類搭配到的加載
類和分類搭配加載
首先配置環(huán)境主類 LGPerson
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson : NSObject
@property (nonatomic, copy) NSString *kc_name;
@property (nonatomic, assign) int kc_age;
- (void)kc_instanceMethod1;
- (void)kc_instanceMethod3;
- (void)kc_instanceMethod2;
+ (void)kc_sayClassMethod;
@end
NS_ASSUME_NONNULL_END
#import "LGPerson.h"
@implementation LGPerson
+ (void)load{
}
- (void)kc_instanceMethod3{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)kc_instanceMethod2{
NSLog(@"%s",__func__);
}
+ (void)kc_sayClassMethod{
NSLog(@"%s",__func__);
}
@end
分類1 LGPerson+LGA
#import "LGPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson (LGA)
- (void)cateA_1;
- (void)cateA_2;
- (void)cateA_3;
@end
NS_ASSUME_NONNULL_END
#import "LGPerson+LGA.h"
@implementation LGPerson (LGA)
+ (void)load{
}
- (void)kc_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cateA_2{
NSLog(@"%s",__func__);
}
- (void)cateA_1{
NSLog(@"%s",__func__);
}
- (void)cateA_3{
NSLog(@"%s",__func__);
}
@end
分類2 LGPerson+LGB
#import "LGPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface LGPerson (LGB)
- (void)cateB_1;
- (void)cateB_2;
- (void)cateB_3;
@end
NS_ASSUME_NONNULL_EN
#import "LGPerson+LGB.h"
@implementation LGPerson (LGB)
+ (void)load{
}
- (void)kc_instanceMethod1{
NSLog(@"%s",__func__);
}
- (void)cateB_2{
NSLog(@"%s",__func__);
}
- (void)cateB_1{
NSLog(@"%s",__func__);
}
- (void)cateB_3{
NSLog(@"%s",__func__);
}
@end
1.非懶加載類 + 非懶加載分類
-
首先 在 realizeClassWithoutSwift 寫下針對性研究LGPerson類的代碼 并在里斷點 看其調用堆棧
-
繼續(xù) 在methodizeClass 寫下針對性代碼 并在里斷點 看其調用堆棧
- 繼續(xù) 在attachToClass 寫下針對性代碼 并在里斷點 看其調用堆棧
-
在下面打上新的斷點 并 在 attachCategories里到打下斷點
過斷點 發(fā)現(xiàn) 上面我們標紅區(qū)域并沒有走 但是確實也來到了 attachCategories方法 確實也是LGPerson 我們仔細看堆棧情況
(1). 此時發(fā)現(xiàn) map_image掉起的流程已經(jīng)結束 并沒有調用 attachCategories汰寓。流程: map_image -> _read_images() -> realizeClassWithoutSwift -> methodizeClass ->attachToClass ->結束
(2).而是由 load_image發(fā)起的調用流程:load_image -> loadAllCategories ->load_categories_nolock -> attachCategories
(3).此時 rwe 有了值 我們bt 打印其 methods 發(fā)現(xiàn)其正是 對 ro->methods的數(shù)據(jù)拷貝 對于extAllocifNeeded()我們上面 也已經(jīng)詳細分析過了。
在下面繼續(xù)斷點跟蹤
1.發(fā)現(xiàn) cats_count =1 此時循環(huán)一次 分類的名字 為LGA 將分類中的方法 存入mists 的最后一位 上面有對此詳細的解答
- 將分類的方法 進行排序 并將 分類的方法存入到 rwe中 通過 attachLists方法 上面我們也有對attachLists算法的詳細解答
我們斷過上面 來到 1399 1400 方法 并將 1367 和1399 1400 斷點取消
繼續(xù)過斷點操作 發(fā)現(xiàn)此時斷點又斷回了attachCategories 中的針對性代碼區(qū)域
繼續(xù)打開 1367 1399 1400 向下走
1.發(fā)現(xiàn) cats_count =1 此時循環(huán)一次 分類的名字 為LGB 將分類中的方法 存入mists 的最后一位
- 將分類的方法 進行排序 并將 分類的方法存入到 rwe中 通過 attachLists方法
繼續(xù)取消 1367 和1399 1400 斷點 向下走 發(fā)現(xiàn)又回到了 realizeClassWithoutSwift
斷點到針對性代碼下面
繼續(xù)過斷點 發(fā)現(xiàn)再此處 cls->isRealized() 判斷了一下是否實現(xiàn) 實現(xiàn)返回cls 并沒有向下執(zhí)行 又回到了 realizeClassWithoutSwift方法對性代碼里
繼續(xù)過斷點 從判斷 cls->isRealized() 直接返回cls 整個流程跑完了程序進入了main函數(shù)
注:刪掉其他斷點留下針對性調試的斷點 我們下面還會用哦
總結:非懶加載類+ 非懶加載分類 : 首先map_image -> _read_images() -> realizeClassWithoutSwift -> methodizeClass ->attachToClass ->結束
在這條支線上并未對 分類進行什么操作 只是完成了類的加載
然后發(fā)起 load_image函數(shù)的回調 -> loadAllCategories ->load_categories_nolock -> attachCategories (在這里有多次調用有幾個非懶分類會調用幾次)將分類的數(shù)據(jù)貼到 rwe中苹粟,并再次發(fā)起了 realizeClassWithoutSwift(有幾個非懶分類會調用幾次)的調用 來判斷類是否已經(jīng)實現(xiàn)有滑。
2.非懶加載類 + 懶加載分類
-
下面我們將源碼中 所有分類的load都去掉 使分類變?yōu)閼屑虞d概念 并運行 來到 realizeClassWithoutSwift 此時是由 map_images發(fā)起的調用
-
繼續(xù)過斷點 來到methodizeClass
-
繼續(xù)過斷點 來到attachToClass
-
將下面斷點打開 并運行 發(fā)現(xiàn)并沒有調用 attachCategories 中斷
總結: 非懶加載類 + 懶加載分類 map_image -> _read_images() -> realizeClassWithoutSwift -> methodizeClass ->attachToClass ->結束
3.懶加載類 + 懶加載分類
-
下面將主類和分類里所有的load方法全部去掉 使其變?yōu)樗^的懶加載 運行 來到 realizeClassWithoutSwift
發(fā)現(xiàn)此時的 realizeClassWithoutSwift 是由消息慢速查找流程里的lookUpImpOrForward掉起的。這說明在第一次消息 調用時候加載數(shù)據(jù)
-
我們繼續(xù)向下走 來到methodizeClass 方法
-
繼續(xù)向下走 來到attachToClass 方法
-
打開下圖所示斷點 看是否可以進去調用 attachCategories
- 發(fā)現(xiàn)并未進去 此時運行完畢
總結流程: lookUpImpOrForward -> realizeClassMaybeSwiftMaybeRelock ->realizeClassWithoutSwift->methodizeClass->走完
4.懶加載類 + 非懶加載分類 (經(jīng)測試 一個類擁有多個分類的情況下只要有大于等于兩個分類實現(xiàn)load就會走下面流程)
-
下面將主類去掉 load 嵌削,分類全部加上load 我們再次分析
運行發(fā)現(xiàn)奔潰 去掉 針對性代碼的元類判斷 我們根據(jù)斷點打印查看當前是否為 LGPerson
-
繼續(xù)運行 來到 realizeClassWithoutSwift
我們看調用棧 realizeClassWithoutSwift 是由 load_images發(fā)起的
-
繼續(xù)運行來到 methodizeClass
-
繼續(xù)運行來到 attachToClass
-
打開下面斷點看其是否可以進去
-
繼續(xù)運行 它進來了
*繼續(xù)過斷點 來到了 attachCategories
-
繼續(xù)向下走
(1) .此時 cats_count = 2 因為兩個分類 (還記得 非懶加載類+非懶加載分類 時 cats_count是=1 而是 調用了兩遍 attachCategories方法毛好,也就是 調用一次 初始化一個64位的棧 并將方法 分類方法存入此64位的最后一位)
(2) .此時 for循環(huán)2遍 拿到 兩個分類的 mlist 存入 mlists[64 - ++mcount ] 先進來的放在最后面 第二次進來的放在倒數(shù)第二未
(3).分類方法排序prepareMethodLists
(4). 通過attachLists方法 將mlists插入 rwe的方法列表
總結
- 懶加載類 + 非懶加載分類 調用 load_image函數(shù)的回調 -> prepare_load_methods ->realizeClassWithoutSwift ->methodizeClass -> attachToClass -> 并進入到 分類附加方法里 調用->attachCategories -> for循環(huán) 分類個數(shù)次數(shù) ; 分類方法mlist 存入 mlists[64 - ++mcount ] 先進來的放在最后面 第二次進來的放在倒數(shù)第二 -> 1.將mlists方法 序列化 ;2.通過attachLists算法將 分類的方法 貼到rwe之中 -> realizeClassWithoutSwift 判斷是否實現(xiàn)了cls實現(xiàn)返回 進入 main函數(shù)
懶加載類 + 非懶加載分類 + 懶加載分類(這種情況多個分類的情況下 有且只有一個分類為非懶加載才會走下面)
這種情況當由主類沒有實現(xiàn)load 分類 兩個其中的一個實現(xiàn)load 另一個不實現(xiàn)
-
運行發(fā)現(xiàn)斷到realizeClassWithoutSwift是由 map_images掉起的
-
過斷點來到 methodizeClass
-
過斷點來到 attachToClass
-
繼續(xù)向下過斷點 看是否會進入到下邊
*繼續(xù)向下過斷點 發(fā)現(xiàn)并未進入到 下方 attachCategories 取消斷點繼續(xù)運行 進入到了main
總結:懶加載類 + 非懶加載分類 + 懶加載分類 流程 map_images -> realizeClassWithoutSwift -> methodizeClass -> attachToClass ->進入main并未發(fā)現(xiàn) attachCategories的調用
最后總結
我們將這幾種情況的調用流程拿出來
1.非懶加載類 + 非懶加載分類
map_image -> _read_images() -> realizeClassWithoutSwift -> methodizeClass
load_image -> loadAllCategories ->load_categories_nolock -> attachCategories
2.非懶加載類 + 懶加載分類
map_image -> _read_images() -> realizeClassWithoutSwift -> methodizeClass
3.懶加載類 + 懶加載分類
lookUpImpOrForward -> realizeClassMaybeSwiftMaybeRelock ->realizeClassWithoutSwift->methodizeClass
4.懶加載類 + 非懶加載分類(多個分類大于等于兩個實現(xiàn)load流程)
load_image -> prepare_load_methods ->realizeClassWithoutSwift ->methodizeClass -> attachToClass ->attachCategories
5.懶加載類 + 非懶加載分類 + 懶加載分類(多個分類有且只有一個分類實現(xiàn)load)
map_images ->_read_images()-> realizeClassWithoutSwift -> methodizeClass
可見load的非必須不要隨意用 肌访,因為 在main函數(shù)之前會做很多很多事情。