目錄
- 一、前言
- 二、繼續(xù)探索_read_images方法
- 三、realizeClassWithoutSwift方法分析
- 四、methodizeClass方法分析
- 五、懶加載類與非懶加載類
- 六、分類探索
- 七、如何將分類中的方法添加到類中
- 總結(jié)
一、前言
上一篇文章iOS dyld和objc的關(guān)聯(lián)分析(類的加載上)中我們分析了dyld和objc的關(guān)聯(lián)關(guān)系,但是類中的方法、屬性、協(xié)議什么時(shí)候添加到類中的,rwe什么時(shí)候產(chǎn)生的這些問(wèn)題依然沒(méi)有解決。那么這篇文章我們就繼續(xù)往下探索。
二、繼續(xù)探索_read_images方法
這里重點(diǎn)分析_read_images方法中處理non-lazy classes的代碼
通過(guò)上篇文章的分析我們從macho文件中讀取到類的地址和名字,接下來(lái)處理類中的ro、rw、rwe等數(shù)據(jù)。
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
// +load handled by prepare_load_methods()
// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
classref_t const *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
if (!cls) continue;
addClassTableEntry(cls);
if (cls->isSwiftStable()) {
if (cls->swiftMetadataInitializer()) {
_objc_fatal("Swift class %s with a metadata initializer "
"is not allowed to be non-lazy",
cls->nameForLogging());
}
// fixme also disallow relocatable classes
// We can't disallow all Swift classes because of
// classes like Swift.__EmptyArrayStorage
}
realizeClassWithoutSwift(cls, nil);
}
}
ts.log("IMAGE TIMES: realize non-lazy classes");
...
}
重點(diǎn)方法:realizeClassWithoutSwift
三、realizeClassWithoutSwift方法分析
注意:realizeClassWithoutSwift方法調(diào)用的前提是這個(gè)類是非懶加載類
/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
runtimeLock.assertLocked();
class_rw_t *rw;
Class supercls;
Class metacls;
if (!cls) return nil;
if (cls->isRealized()) return cls;
ASSERT(cls == remapClass(cls));
// fixme verify class is not in an un-dlopened part of the shared cache?
auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro();
ASSERT(!isMeta);
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = objc::zalloc<class_rw_t>();
rw->set_ro(ro);
rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
cls->setData(rw);
}
...
遞歸調(diào)用realizeClassWithoutSwift方法實(shí)現(xiàn)父類及元類
// cls 信息 -> 父類 -> 元類 : cls LGPerson
supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
...
// Attach categories - 分類
methodizeClass(cls, previously);
return cls;
}
realizeClassWithoutSwift方法注釋如下:
- 在類
cls上執(zhí)行首次初始化, - 包括分配它的讀寫數(shù)據(jù)。
- 不執(zhí)行任何快速端初始化。
- 返回類的真實(shí)類結(jié)構(gòu)。
- 鎖定:
runtimeLock必須由調(diào)用者寫鎖
技巧:在realizeClassWithoutSwift方法中加入如下代碼并打上斷點(diǎn)即可只研究我們自定義的類

clean memory:加載后不會(huì)發(fā)生更改的內(nèi)存
dirty memory:加載進(jìn)程運(yùn)行時(shí)會(huì)發(fā)生更改的內(nèi)存。類結(jié)構(gòu)一經(jīng)使用就會(huì)變成dirty memory ,因?yàn)檫\(yùn)行時(shí)會(huì)向它寫入新的數(shù)據(jù)(eg:方法緩存)。
ro:read only-只讀的,保留類的原始數(shù)據(jù)
rw:read write-外界通過(guò)rw查找方法,首先判斷是否存在rwe,有就從rwe中尋找方法,沒(méi)有就從ro中尋找方法。
rwe:2020新增內(nèi)容,由于rw中的數(shù)據(jù)和ro存在重復(fù)造成了內(nèi)存浪費(fèi),而且只有運(yùn)行時(shí)動(dòng)態(tài)修改類的信息才會(huì)造成兩者數(shù)據(jù)存在差異。為了節(jié)省內(nèi)存從rw中分離出會(huì)變化的rwe,創(chuàng)建rwe時(shí)首先會(huì)copy一份ro中的數(shù)據(jù),然后在修改或新增數(shù)據(jù)。因此使用了Runtime修改了的類才有rwe數(shù)據(jù)。
關(guān)于
ro、rw、rwe的官方介紹:WWDC 2020 :Advancements in the Objective-C runtime



四、methodizeClass方法分析
在realizeClassWithoutSwift方法中設(shè)置了ro、rw并在最后調(diào)用了methodizeClass方法,由此我們猜想methodizeClass方法中設(shè)置了rwe數(shù)據(jù)。
/***********************************************************************
* methodizeClass
修復(fù)cls的方法列表、協(xié)議列表和屬性列表。
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
// realizeClassWithoutSwift 盡管看到了 data() -> ro -> rw (rwe)
// ro - methodlist - 方法查找的時(shí)候 (二分查找) sel 排序
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();
const char *mangledName = cls->mangledName();
const char *LGPersonName = "LGPerson";
if (strcmp(mangledName, LGPersonName) == 0) {
bool kc_isMeta = cls->isMetaClass();
auto kc_rw = cls->data();
auto kc_ro = kc_rw->ro();
if (!kc_isMeta) {
printf("%s: 這個(gè)是我要研究的 %s \n",__func__,LGPersonName);
}
}
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (rwe && proplist) {
rwe->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (rwe && protolist) {
rwe->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
#if DEBUG
// Debug: sanity-check all SELs; log method list contents
for (const auto& meth : rw->methods()) {
if (PrintConnecting) {
_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-',
cls->nameForLogging(), sel_getName(meth.name));
}
ASSERT(sel_registerName(sel_getName(meth.name)) == meth.name);
}
#endif
}
方法排序的步驟:
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle)
{
...
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
...
}
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
...
// Sort by selector address.
if (sort) {
method_t::SortBySELAddress sorter;
std::stable_sort(mlist->begin(), mlist->end(), sorter);
}
}
...
所以類中的方法列表是通過(guò)方法地址
SEL排序的
雖然這里看到了rwe但是rwe卻是NULL

因此
methodizeClass方法中還是沒(méi)有設(shè)置rwe
五、懶加載類與非懶加載類
- 非懶加載類:實(shí)現(xiàn)了
+load方法(會(huì)提前加載,load_images方法中會(huì)調(diào)用所有+load方法) - 懶加載類: 未實(shí)現(xiàn)
+load方法
下面研究懶加載類在什么時(shí)候加載?
準(zhǔn)備工作:
- 注釋
+load方法
//+ (void)load{
//
//}
2.添加斷點(diǎn)

-
realizeClassWithoutSwift方法處理
因?yàn)橐虞d類就必然會(huì)調(diào)用realizeClassWithoutSwift方法,所以在該方法中也添加斷點(diǎn)
運(yùn)行代碼

- 由于懶加載類先執(zhí)行
main中的代碼再調(diào)用realizeClassWithoutSwift方法加載類,所以程序先在main函數(shù)中中斷
繼續(xù)調(diào)試

打印出堆棧信息

2.通過(guò)堆棧信息可知當(dāng)調(diào)用LGPerson的alloc方法后觸發(fā)消息發(fā)送->消息轉(zhuǎn)發(fā)->類的加載
現(xiàn)在我們知道了
懶加載類的加載時(shí)機(jī)在于類的第一次消息發(fā)送
- 懶加載類和非懶加載類方法調(diào)用情況:

實(shí)際調(diào)用堆棧如下:

調(diào)用順序?yàn)椋?code>map_images->_read_images->readClass->realizeClassWithoutSwift->methodizeClass
修改:

六、分類探索
由于在realizeClassWithoutSwift方法的最后調(diào)用了methodizeClass方法:
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
...
// Attach categories - 分類
methodizeClass(cls, previously);
return cls;
}
methodizeClass方法上面注釋了Attach categories,所以我們先來(lái)分析分類的結(jié)構(gòu)
準(zhǔn)備如下代碼:

通過(guò)clang將main.m生成cpp文件:
命令行代碼如下:
cd main.m目錄
clang -rewrite-objc main.m -o main2.cpp
通過(guò)生成的cpp文件我們可以發(fā)現(xiàn)LGPerson分類轉(zhuǎn)換為了如下C++代碼

其數(shù)據(jù)結(jié)構(gòu),也就是分類的本質(zhì)為:_category_t

-
_category_t結(jié)構(gòu)體中包含了兩個(gè)_method_list_t一個(gè)是實(shí)例變量的方法列表,一個(gè)是類的方法列表 - 由于分類沒(méi)有協(xié)議,所以
protocols賦值為0,(cls賦值也為0,會(huì)在運(yùn)行時(shí)賦上正確的值) - 由于結(jié)構(gòu)體中沒(méi)有成員變量列表,所以分類也不能添加成員變量
instance_methods、class_methods、properties對(duì)應(yīng)的C++代碼如下:

可以發(fā)現(xiàn)
instance_methods中并沒(méi)有屬性cate_name、cate_age的set、get方法。所以分類中可以添加屬性但是不會(huì)生成對(duì)應(yīng)的set、get方法,只能通過(guò)Runtime動(dòng)態(tài)添加set、get方法。-
_method_list_t中的數(shù)據(jù)結(jié)構(gòu)為method_t
method_t
現(xiàn)在我們明白了分類的底層結(jié)構(gòu),那么分類是如何添加到類里面去的呢?
七、如何將分類中的方法添加到類中
由于之前我們猜想methodizeClass方法中設(shè)置了rwe數(shù)據(jù),那么現(xiàn)在繼續(xù)探索methodizeClass方法后面的代碼,我們注意到有這樣的代碼:
// Attach categories.
if (previously) {
if (isMeta) {
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_METACLASS);
} else {
// When a class relocates, categories with class methods
// may be registered on the class itself rather than on
// the metaclass. Tell attachToClass to look for those.
objc::unattachedCategories.attachToClass(cls, previously,
ATTACH_CLASS_AND_METACLASS);
}
}
objc::unattachedCategories.attachToClass(cls, cls,
isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
斷點(diǎn)調(diào)試進(jìn)入methodizeClass方法

通過(guò)調(diào)試我們可以發(fā)現(xiàn):
-
method_list_t *list中只有類本身的方法,還沒(méi)添加分類中的方法 - 雖然
rwe為NULL但執(zhí)行了prepareMethodLists方法將類中的方法進(jìn)行了排序
繼續(xù)調(diào)試我們可以發(fā)現(xiàn)if中的代碼不會(huì)執(zhí)行:

然而進(jìn)入attachToClass方法后里面也沒(méi)執(zhí)行if中的attachCategories方法

重點(diǎn)方法attachCategories
在attachCategories中添加如下代碼并加上斷點(diǎn):
if (strcmp(mangledName, LGPersonName) == 0) {
bool kc_isMeta = cls->isMetaClass();
auto kc_rw = cls->data();
auto kc_ro = kc_rw->ro();
if (!kc_isMeta) {
printf("%s: 這個(gè)是我要研究的 %s \n",__func__,LGPersonName);
}
}
放開(kāi)methodizeClass中的斷點(diǎn)繼續(xù)運(yùn)行會(huì)發(fā)現(xiàn)會(huì)進(jìn)入attachCategories方法

調(diào)用堆棧如下:

由此可得出以下結(jié)論:
- 雖然實(shí)現(xiàn)類調(diào)用的
methodizeClass->attachToClass后沒(méi)調(diào)用attachCategories方法,但是load_images->loadAllCategories后會(huì)調(diào)用attachCategories方法。 -
attachCategories方法中進(jìn)行了rwe初始化。
繼續(xù)運(yùn)行:

這里可以看到分類在運(yùn)行時(shí)會(huì)修改category_t中name:由LGPerson改為LGA,并為cls賦值。
在attachCategories方法中通過(guò)調(diào)用extAllocIfNeeded()生成了rwe,因此調(diào)用extAllocIfNeeded()的地方就會(huì)生成了rwe。全局搜索extAllocIfNeeded ()即可發(fā)現(xiàn)attachCategories、addMethod、class_addProtocol、_class_addProperty等地方調(diào)用了extAllocIfNeeded()。
如果類在有
分類,動(dòng)態(tài)添加方法、協(xié)議、屬性的情況下才會(huì)生成rwe。
現(xiàn)在我們知道了什么添加分類,什么時(shí)候初始化rwe,但是什么時(shí)候?qū)⒎诸愄砑拥筋愔胁恢?,后續(xù)文章將繼續(xù)探索。
總結(jié)
- 非懶加載類在
main函數(shù)之前加載(所以+load方法非必要不要實(shí)現(xiàn)) - 懶加載類的加載推遲到類的第一次消息發(fā)送
- 分類中可以添加
屬性,但不會(huì)自動(dòng)生成set、get方法

