前言
iOS 底層第16
天的學習哥力。接著 15
天學習的內容。分析 category
是如何加載到 class
里的泣侮。
category 探索
- 我們回到
realizeClassWithoutSwift
static Class realizeClassWithoutSwift(Class cls, Class previously) {
// ...
// Attach categories
methodizeClass(cls, previously);
// ...
}
- 進入
methodizeClass
static void methodizeClass(Class cls, Class previously)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
// ...
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
// ....
-
if (rwe)
有值rwe
就會methods.attachLists
局雄,進去反推rwe = rw->ext()
,那ext()
在哪里實現(xiàn)的? - 進入
ext()
class_rw_ext_t *ext() const {
return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}
- 搜索
class_rw_ext_t
class_rw_ext_t *extAllocIfNeeded() {
auto v = get_ro_or_rwe();
if (fastpath(v.is<class_rw_ext_t *>())) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
} else {
return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
}
}
- 找到
extAllocIfNeeded
一疯,全局搜索extAllocIfNeeded
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
// ...
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
auto rwe = cls->data()->extAllocIfNeeded();
// ...
}
- 發(fā)現(xiàn)在
attachCategories
里有對extAllocIfNeeded
進行調用 - 全局搜索
attachCategories
撼玄, 發(fā)現(xiàn)有2
個地方調用了attachCategories
- 第一處:
attachToClass
void attachToClass(Class cls, Class previously, int flags)
{
// ...
auto &map = get();
auto it = map.find(previously);
if (it != map.end()) {
category_list &list = it->second;
if (flags & ATTACH_CLASS_AND_METACLASS) {
int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
} else {
attachCategories(cls, list.array(), list.count(), flags);
}
map.erase(it);
}
}
- 第二處:
load_categories_nolock
static void load_categories_nolock(header_info *hi) {
size_t count;
auto processCatlist = [&](category_t * const *catlist) {
for (unsigned i = 0; i < count; i++) {
category_t *cat = catlist[I];
Class cls = remapClass(cat->cls);
locstamped_category_t lc{cat, hi};
// ...
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
if (cls->ISA()->isRealized()) {
// 調用了
attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);
} else {
objc::unattachedCategories.addForClass(lc, cls->ISA());
}
}
}
}
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}
- 先從
attachToClass
進行推導,全局搜索attachToClass
static void methodizeClass(Class cls, Class previously)
{
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);
if (rwe) rwe->methods.attachLists(&list, 1);
}
// ... 省略部分代碼
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
}
- 最后發(fā)現(xiàn)又回到了
methodizeClass
- 正向流程應該是:
realizeClassWithoutSwift
->methodizeClass
->attachToClass
->attachCategories
- 然后這個
attachCategories
內部到底做了哪些事情呢墩邀?我們繼續(xù)進行分析
attachCategories
- 進入
attachCategories
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
// ...
uint32_t mcount = 0;
uint32_t propcount = 0;
uint32_t protocount = 0;
bool fromBundle = NO;
bool isMeta = (flags & ATTACH_METACLASS);
auto rwe = cls->data()->extAllocIfNeeded();
// ...
if (mcount > 0) {
// 方法的排序
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
NO, fromBundle, __func__);
// 方法的添加
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) {
flushCaches(cls, __func__, [](Class c){
// constant caches have been dealt with in prepareMethodLists
// if the class still is constant here, it's fine to keep
return !c->cache.isConstantOptimizedCache();
});
}
}
// . ..
}
- 進入
methods.attachLists
進行分析
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; I--)
newArray->lists[i + addedCount] = array()->lists[I];
for (unsigned i = 0; i < addedCount; I++)
newArray->lists[i] = addedLists[I];
free(array());
setArray(newArray);
validate();
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
validate();
}
else {
// 1 list -> many lists
Ptr<List> oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
for (unsigned i = 0; i < addedCount; I++)
array()->lists[i] = addedLists[I];
validate();
}
}
- 我們先從最短的開始分析
// 0 lists -> 1 list
list = addedLists[0];
validate();
- 當新添加只有
1
個并且lists 無值
的時候掌猛,list
=addedList
- 接著從
else
開始分析
// 1 list -> many lists
Ptr<List> oldList = list;
uint32_t oldCount = oldList ? 1 : 0; // oldList 有數(shù)據(jù) => oldCount = 1
uint32_t newCount = oldCount + addedCount; // 假設 addedCount =2 ,newCount = 1+2 = 3
setArray((array_t *)malloc(array_t::byteSize(newCount))); // 創(chuàng)建一個新的array
array()->count = newCount; // 新的 array 空間大小 = 3
if (oldList) array()->lists[addedCount] = oldList; // 把 oldlist 放到 lists[2] 位置里
for (unsigned i = 0; i < addedCount; I++)
array()->lists[i] = addedLists[I]; // 循環(huán)從第0位置開始放入到lists眉睹,放入個數(shù)為addedCount = 2
validate();
- 總結一下就是把
oldlists 這個整體
放到newlists
最后面荔茬,把added 數(shù)據(jù)
放到newlsit
的最前面 - 接著繼續(xù)添加就會進入
if (hasArray())
,開始分析if (hasArray())
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount)); // 開辟一個新的內存空間
newArray->count = newCount;
array()->count = newCount;
for (int i = oldCount - 1; i >= 0; i--) // 倒序 ,假設oldCount = 3 辣往,addedCount = 1, i = 2
newArray->lists[i + addedCount] = array()->lists[i]; //第一次循環(huán)兔院,newlists[2 + 1 = 3] =殖卑, lists[2] 站削,就是把list最后元素 加到 newlist 的最后面
for (unsigned i = 0; i < addedCount; I++)
newArray->lists[i] = addedLists[I]; // 把 added 數(shù)據(jù)加到 newlist 的前面
free(array()); // 情況數(shù)據(jù)重新開始
setArray(newArray);
validate();
- 總結一下就是把
lists 數(shù)據(jù)
倒序依次放到newlists
最后面,把added 數(shù)據(jù)
放到newlists
的最前面 - ps:圖畫的有點丑請見諒
動態(tài)調試分析 attachCategories
- 分析前準備新建一個
XKStudent (XKA)
類目孵稽,主類
和分類
同時調用load
方法
@implementation XKStudent
- (void) doSomething {}
- (void) doSomething2 {}
+ (void) load {
NSLog(@"加載了 %s",__func__);
}
@end
// 分類
@implementation XKStudent (XKA)
+ (void) load {
NSLog(@"加載了 %s",__func__);
}
- (void) doSomethingByCate {}
- (void) doSomething2ByCate {}
@end
- 打印流程如下
- 這時我們發(fā)現(xiàn)當
主類
和分類
同時調用load
方法時许起,會調用load_categories_nolock
- 開始分析,在哪里調用了
load_categories_nolock
查看堆棧信息菩鲜, 得知在
load_images
->loadAllCategories
->load_categories_nolock
最終來到了attachCategories
這時得出一個結論就是:
類
與分類
同時調用load
方法
從_read_image
開始 調用realizeClassWithoutSwift
->methodizeClass
->attachToClass
最終沒有進入attachCategories
而是在
load_images
->loadAllCategories
->load_categories_nolock
->attachCategories
探索到這里也就能解釋了之前為何
attachCategories
會有2
個地方(1.attachToClass
,2: load_categories_nolock
)調用的原因了园细。進入
load_categories_nolock
- 打印輸出
cat
驗證cat
是我們這次要分析Student(XKA)
- 繼續(xù)往下分析
- 根據(jù)判斷條件進入
attachCategories
,來到 ??
- 分析代碼
mlists[ATTACH_BUFSIZ - ++mcount] = mlist
,輸出打印結果
- 把
mlist
放到了mlists
的 第63
個位置 - 繼續(xù)往下分析接校,來到??
-
mlists + ATTACH_BUFSIZ - mcount
這個是猛频?
,我們打印輸出一下
- 這下我們就能得知原來·
attachLists
里存放的指針的指針
- 接下我們動態(tài)調試再次進入了
attachLists
- 這時我們已得知
addedLists
是一個二級指針
蛛勉,·addedCount = 1
- 繼續(xù)往下分析來到了
else
??
- 這里的
list
應該是主類 - XKStudent
鹿寻,打印驗證下
- 而這里的
array()->lists[addedCount]
存的是什么呢?
- 打印
array()->lists[addedCount]
- 打印
addedList
- 由此得知
array()->lists[i]
里其實就是存了一個數(shù)組指針
诽凌,而指針指向的地址就是methods_list_t
, 由此就能得知array()->lists[0]
指針指向就是一個category:methods_list
??
已知在
attachLists
方法里還有一個if (hasArray()) {}
毡熏,那何時會動態(tài)分析進入呢?
猜想如果定義多個category
是不是就會進入呢侣诵?
- 分析前準備新建
3個
分類
// XKA
@interface XKStudent (XKA)
- (void) doSomethingByCateA;
- (void) doSomething2ByCateA;
@end
// XKB
@interface XKStudent (XKB)
- (void) doSomethingByCateB;
- (void) doSomething2ByCateB;
@end
// XKC
@interface XKStudent (XKC)
- (void) doSomethingByCateC;
- (void) doSomething2ByCateC;
@end
- 動態(tài)分析進入
load_categories_nolock
- 打印輸出
count
,catelist[i]
驗證是不是要分析的對象
-
count = 3
說明有3 個 cate
痢法,分別是catelist[0] = XKC
,catelist[1] = XKA
,catelist[2] = XKB
- 繼續(xù)分析進入
attachLists
- 得知
oldCount = 2
說明array()->lists
里已經(jīng)有2個數(shù)據(jù)了狱窘,我們輸出來驗證一下
- 繼續(xù)往下走
- 打印輸出
newArray->lists
- 由輸出可知
oldlists ptr
倒序插入到了newlists ptr
,再把addedlists
插入到newlists 第0個
- 最后得出的結論就是:當
oldArray()
里已有一個主類
和至少有一個分類
會進入if (hasArray())
里 财搁,然后在0號位置
繼續(xù)插入新的分類
蘸炸。
??的分析和探索都是基于
主類
和分類
都同時調用了load
方法。那如果有一種情況不滿足會如何呢尖奔?繼續(xù)進行探索
load 探索
1.主類 load 分類 no load
- 動態(tài)調試日志??
- 根據(jù)打印的日志幻馁,發(fā)現(xiàn)并沒有進入
attachCategories
2.主類 no load 分類 load
- 動態(tài)調試日志??
- 根據(jù)打印的日志,發(fā)現(xiàn)
也
沒有進入attachCategories
3.主類 no load 分類 no load
- 動態(tài)調試日志??
- 在一次發(fā)生消息的時候時會進入到
realizeClassWithoutSwift
4.主類 load 多個分類不都有 load
- 可知流程
realizeClassWithoutSwift
->methodizeClass
->attachToClass
->attachCategories
5.主類 no load 多個分類不都有 load
- 可知流程
realizeClassWithoutSwift
->methodizeClass
->attachToClass
->attachCategories
- 這下我們就能得知
主類
雖然no load
,但分類 load
了還是會進入attachCategories
越锈。有種被迫的感覺
那如果沒有進入
attachCategories
仗嗦,分類
里的數(shù)據(jù)到底是何時加載到類
里的呢?
進入
realizeClassWithoutSwift
分情況進行驗證-
1:主類 load 分類 no load
- 斷點輸出
ro
內部方法
- 斷點輸出
-
2:主類 no load 分類 load
- 斷點輸出
ro
內部方法
- 斷點輸出
-
3:主類 no load 分類 no load
- 斷點輸出
ro
內部方法
- 斷點輸出
- 整理:
1甘凭,2稀拐,3
在一開始就存在了data()
里,都是在一開始已經(jīng)從disk
里讀取到了分類
的數(shù)據(jù)丹弱。data()
直接從macho
里加載數(shù)據(jù)
總結
- 我們今天從
realizeClassWithoutSwift
會切入點德撬,探索了分類
到底是何時進行加載的 - 當在
主類
和分類
同時調用load
方法時,會進入一段很繁瑣
的加載流程
(_read_image -> realizeClassWithoutSwift -> methodizeClass
->attachToClass
load_images
->loadAllCategories
->load_categories_nolock
->attachCategories
)
目的就是把存放list 的指針數(shù)組
加入到rwe
里 - 當在
主類
和分類
不同時調用load
方法時躲胳,就會從macho
直接讀取放到data()
里蜓洪,再從data()
讀取rwe
數(shù)據(jù) - 得出最主要的
結論
就是在開發(fā)過程中盡量不要在主類和分類同時加載 load 方法
知識點補充
ro rw rwe
你是否會有同樣的
?
,ro rw rwe
到底是什么坯苹?為什么 要有rwe
呢隆檀?
名稱 | 解釋 | 描述 | 來源 |
---|---|---|---|
ro | read only 只讀 |
運動時內存是不會發(fā)生變化恐仑,被稱為 clean memory:干凈內存
|
從 disk 讀取 |
rw | read write 讀寫 |
運行時會被寫入新的數(shù)據(jù),因此它非常的 昂貴 为鳄,被稱為 dirty memory 臟內存
|
ro 的 copy
|
rwe |
rw 的擴展 |
rw 內存非常昂貴 ,rwe 優(yōu)化 rw
|
rw |
rwe
是怎么優(yōu)化rw
呢裳仆?
Apple develop
有個運行時機制,它能通過擴展
,底層 api
動態(tài)增加 rw
的大小孤钦,而 rw
又十分的 昂貴
歧斟。而且我們還發(fā)現(xiàn)并不是每個 class
都需要 擴展
或是 動態(tài)api
,只有在需要時候才去分配 內存
偏形,因此引入了 rwe
静袖。
cls ->Data() 探索
在
realizeClassWithoutSwift
會有auto ro = (const class_ro_t *)cls->data();
那為何cls->data()
就能轉成成ro
呢?
- 動態(tài)調試把程序運行看看
cls->data()
里到底是什么壳猜,斷點來到??
- 進入
data()
-
p
輸出bit->data
,x/8gx
輸出cls
??
發(fā)現(xiàn)
data()
就是地址指針勾徽,存放在cls
里第5
片內存里
那為何能轉成
class_ro_t
這個結構呢?
- 這個最主要的原因就是
Apple
在llvm編譯時
做了數(shù)據(jù)匹配相應的處理。只要地址指針里的格式
與結構體的格式
是相互匹配的喘帚,就是進行結構體指針
的賦值操作畅姊。 - 再舉個 ??
int main(int argc, const char * argv[]) {
// runtime 調用 api ,讀取 Method
Method m = class_getInstanceMethod(XKStudent.class, @selector(teacherSay));
}
- 輸出 ??
- 找到
Method
源碼 吹由,自定義一個 類似Method
結構體
struct objc_method {
SEL _Nonnull method_name;
char * _Nullable method_types;
IMP _Nonnull method_imp;
};
int main(int argc, const char * argv[]) {
struct objc_method *method = class_getInstanceMethod(XKStudent.class, @selector(teacherSay));
}
- 輸出 ??
可知
同一個指針地址
只要知道其內部結構
,就能根據(jù)地址
進行賦值-
這下又知道了
1.指針地址不但能進行取值
2:指針地址還能再進行同一個格式的結構體指針進行相應的賦值