
在上一篇文章類加載原理(中)我們探索了非懶加載類的加載原理、懶加載類的加載原理以及分類的一些前期探索。這篇文章我們繼續(xù)看看分類的加載和實(shí)現(xiàn)。
分類探索前言
在上一篇文章我們?cè)?code>methodizeClass方法里,進(jìn)入prepareMethodLists方法處理方法后發(fā)現(xiàn)并不會(huì)走if (rwe) rwe->methods.attachLists(&list, 1);因?yàn)檫@個(gè)時(shí)候rwe為NULL。那我們就要去查看下這個(gè)rwe是什么情況下被賦值呢?我們直接command+點(diǎn)擊跳轉(zhuǎn)查看發(fā)現(xiàn)就在本方法的開(kāi)頭里賦值。
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();
//省略下面代碼
}
從這里我們發(fā)現(xiàn)是從rw->ext()獲取值的,那我們就進(jìn)一步跟蹤ext()去看看
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 *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));
}
}
我們從上面的代碼發(fā)現(xiàn)是個(gè)class_rw_ext_t類型,并且發(fā)現(xiàn)了他的內(nèi)存開(kāi)辟方法extAllocIfNeeded(就在class_rw_ext_t *ext() const下方)。并且在這個(gè)方法里直接去利用get_ro_or_rwe方法就獲取到了rwe,如果不行就直接調(diào)用extAlloc方法alloc一個(gè)。那我們繼續(xù)跟蹤下extAllocIfNeeded這個(gè)方法,看在哪兒調(diào)用了。我們?nèi)炙阉鳎?/p>

經(jīng)過(guò)查看圖中搜索到的地方,排除第一個(gè)上面的方法實(shí)現(xiàn)。其他的分別在:
attachCategories : 添加分類方法
objc_class::demangledName : demangledName方法在處理future類的時(shí)候
class_setVersion : 類版本設(shè)置方法
addMethods_finish :添加方法結(jié)束
class_addProtocol : 類添加協(xié)議方法
_class_addProperty : 類添加屬性方法
objc_duplicateClass : 處理重復(fù)類方法
到這里我們發(fā)現(xiàn)太多方法了,那我們繼續(xù)看methodizeClass方法下面的代碼看看能不能找到一些線索,到最后我們看到attachToClass方法,跟蹤進(jìn)去發(fā)現(xiàn)最后也是調(diào)用了attachCategories:
methodizeClass:
static void methodizeClass(Class cls, Class previously)
{
//省略上面代碼
// 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);
//省略下面代碼
}
在這個(gè)方法里有三處調(diào)用,前兩處收到變量previously的影響,而這個(gè)變量又是從方法里的參數(shù)直接傳進(jìn)來(lái)的。所以我們?nèi)ニ阉?code>methodizeClass方法看看在哪里有傳這個(gè)previously參數(shù)。發(fā)現(xiàn)是在方法realizeClassWithoutSwift里直接傳進(jìn)來(lái)的,而這個(gè)參數(shù)又是從realizeClassWithoutSwift方法作為參數(shù)傳進(jìn)來(lái)的,所以我們繼續(xù)反向查找溯源realizeClassWithoutSwift。搜索結(jié)果我們發(fā)現(xiàn)realizeClassWithoutSwift方法調(diào)用處previously參數(shù)都是傳的nil。所以我們基本可以判定前面if不會(huì)走。我們只需要看最后一個(gè)objc::unattachedCategories.attachToClass方法調(diào)用處。
attachToClass:
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get();
auto it = map.find(previously);
/* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
bool isMeta = (flags & ATTACH_METACLASS);
const char *mangledName = cls->nonlazyMangledName();
//定義自己的類名
const char *ZYPersonName = "ZYPerson";
//比較自己的類名和讀取的是否一致一致就進(jìn)入if
if (strcasecmp(ZYPersonName, mangledName) == 0) {
if (!isMeta) {
printf("ZY 我們需要的信息: %s - - %s\n",__func__,mangledName);
}
}
/* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
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);
}
}
分析:從上面這些方法發(fā)現(xiàn)都是在運(yùn)行時(shí)才調(diào)用的方法,以及一些注釋我們還發(fā)現(xiàn)很多都有用到* fixme來(lái)描述,也就是說(shuō)這個(gè)rwe的創(chuàng)建是放在運(yùn)行時(shí)進(jìn)行動(dòng)態(tài)處理的,這樣符合了WWDC2020里關(guān)于類的變動(dòng)和分析的說(shuō)法。從上面的方法分析我們可以知道只有添加分類最符合我們的要求,因?yàn)閺?code>WWDC2020視頻里講的在添加分類、動(dòng)態(tài)添加方法等地方會(huì)有這個(gè)rwe,并且我們?cè)?code>methodizeClass方法最后也是引導(dǎo)我們走了attachToClass并且在這個(gè)方法最終是調(diào)用方法attachCategories,而分類我們從來(lái)沒(méi)有探索過(guò),也不知道它在什么時(shí)候加載處理的,所以我們?nèi)プ粉櫡诸愡@條線,
類和分類搭配加載
上面我們分析了attachCategories這個(gè)方法的一些內(nèi)容處理,下面我們來(lái)探索下attachCategories加載的流程。我們?nèi)匀蝗炙阉?code>attachCategories看看它在什么時(shí)候被調(diào)用:

從上面的全局搜索我們可以確定調(diào)用的地方有兩個(gè):
attachToClass : 添加分類到類
load_categories_nolock : 加載分類
前期分析和預(yù)備工作
到這里我們不禁要思考我們利用這種方法調(diào)用追溯法是否合適,因?yàn)榘l(fā)現(xiàn)我們這種方法查找到的路有多條,而且我們不知道是否后面還有多分支調(diào)用上面這兩個(gè)方法。如果用這種方法查找追溯會(huì)變得特別復(fù)雜,最理想的情況是跟蹤一個(gè)方法一直只有一條線路。但現(xiàn)在顯然不行,所以我們換一種方法 我們直接去上面兩個(gè)方法和attachCategories里面,添加上一行特殊打印如下:
attachToClass:
void attachToClass(Class cls, Class previously, int flags)
{
runtimeLock.assertLocked();
ASSERT((flags & ATTACH_CLASS) ||
(flags & ATTACH_METACLASS) ||
(flags & ATTACH_CLASS_AND_METACLASS));
auto &map = get();
auto it = map.find(previously);
/* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
bool isMeta = (flags & ATTACH_METACLASS);
const char *mangledName = cls->nonlazyMangledName();
//定義自己的類名
const char *ZYPersonName = "ZYPerson";
//比較自己的類名和讀取的是否一致一致就進(jìn)入if
if (strcasecmp(ZYPersonName, mangledName) == 0) {
if (!isMeta) {
printf("ZY 我們需要的信息: %s - - %s\n",__func__,mangledName);
}
}
/* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
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) {
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
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};
/* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
const char *mangledName = cls->nonlazyMangledName();
//定義自己的類名
const char *ZYPersonName = "ZYPerson";
//比較自己的類名和讀取的是否一致一致就進(jìn)入if
if (strcasecmp(ZYPersonName, mangledName) == 0) {
printf("ZY 我們需要的信息:load_categories_nolock %s - - %s\n",__func__,mangledName);
}
/* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Ignore the category.
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
//省略下方代碼
}
};
processCatlist(hi->catlist(&count));
processCatlist(hi->catlist2(&count));
}
attachCategories:
static void
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();
/* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
const char *mangledName = cls->nonlazyMangledName();
//定義自己的類名
const char *ZYPersonName = "ZYPerson";
//比較自己的類名和讀取的是否一致一致就進(jìn)入if
if (strcasecmp(ZYPersonName, mangledName) == 0) {
if (!isMeta) {
printf("attachCategories - ZY 我們需要跟蹤的信息: %s - - %s\n",__func__,mangledName);
}
}
/* *********************嘗試打印看看我們自己的類能不能進(jìn)來(lái)*****************/
//省略下面代碼
}
然后我們結(jié)合前面文章類的加載步驟我們都在特殊打印的地方添加斷點(diǎn),比如以下方法:
realizeClassWithoutSwift
methodizeClass
load_categories_nolock
attachCategories
然后創(chuàng)建一個(gè)類和一個(gè)分類來(lái)測(cè)試:
我們創(chuàng)建一個(gè)ZYPerson的分類ZYPerson (ZYA)。因?yàn)槲覀冊(cè)谇懊嫣骄款惖募虞d的時(shí)候發(fā)現(xiàn)添加load{}方法會(huì)在類發(fā)消息前就加載了,所以我們?yōu)榱吮WC分類加載進(jìn)行我們也給分類添加上load{}。然后在main.m文件里調(diào)用分類ZYPerson (ZYA)的saySomething方法。
ZYPerson .h:
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface ZYPerson : NSObject
- (void)zyEatSugar;
+ (void)sayHappy;
- (void)zyShowTime;
@end
NS_ASSUME_NONNULL_END
ZYPerson .m:
#import "ZYPerson.h"
@implementation ZYPerson
+(void)load{
}
- (void)zyEatSugar
{
NSLog(@"%s",__func__);
}
+ (void)sayHappy
{
NSLog(@"%s",__func__);
}
- (void)zyShowTime
{
NSLog(@"%s",__func__);
}
@end
ZYPerson (ZYA) .h:
#import "ZYPerson.h"
NS_ASSUME_NONNULL_BEGIN
@interface ZYPerson (ZYA)
- (void)saySomething;
- (void)zyA_instanceMethod1;
- (void)zyA_instanceMethod2;
+ (void)zyA_classMethod1;
+ (void)zyA_classMethod2;
@end
ZYPerson (ZYA) .m:
#import "ZYPerson+ZYA.h"
@implementation ZYPerson (ZYA)
+(void)load{ }
- (void)saySomething
{
NSLog(@"%s",__func__);
}
- (void)zyA_instanceMethod1
{
NSLog(@"%s",__func__);
}
- (void)zyA_instanceMethod2
{
NSLog(@"%s",__func__);
}
+ (void)zyA_classMethod1
{
NSLog(@"%s",__func__);
}
+ (void)zyA_classMethod2
{
NSLog(@"%s",__func__);
}
@end
main.m:
int main(int argc, const char * argv[]) {
@autoreleasepool {
ZYPerson *person = [ZYPerson alloc];
[person saySomething];
}
return 0;
}
1,類有load 、分類有load
而我們?cè)谇懊娴拇驍帱c(diǎn)方法里打印查找ro 或者class信息是否包含了分類的方法。如下圖:




類有
load、分類也有load時(shí),分類在load_categories_nolock方法里才會(huì)加載到ro里。
我們?cè)?code>attachCategories方法斷點(diǎn)的地方利用lldb調(diào)試bt命令查看堆棧的方法查看什么時(shí)候通過(guò)什么路徑調(diào)用了我們的attachCategories方法。
_read_images非懶加載類打??;
realizeClassWithoutSwift方法打??;
methodizeClass方法打?。?br>
以及上面我們分析的attachToClass;
load_categories_nolock方法打印。
最后我們到attachCategories打印里去bt打印堆棧信息。 bt結(jié)果如下:

從上面的打印來(lái)和
bt結(jié)果來(lái)看上面我們這種類和分類的搭配走的方法流程是:_read_images 非懶加載類->realizeClassWithoutSwift->methodizeClass->attachToClass
在這個(gè)地方轉(zhuǎn)折調(diào)用了dyld然后走
load_images->loadAllCategories->load_categories_nolock->attachCategories
2,類無(wú)load 、分類有load
我們把ZYPerson類里面的load方法屏蔽保留分類ZYPerson(ZYA)里的load。其他環(huán)境不變。然后運(yùn)行我們的代碼。結(jié)果如下:

類無(wú)
load、分類有load時(shí),分類中的方法在編譯階段已添加到ro中

類無(wú)
load,分類有load。不會(huì)走到attachCategories方法只會(huì)走類非懶加載流程:(也許這就是被迫營(yíng)業(yè)了吧)
_read_images 非懶加載類->realizeClassWithoutSwift->methodizeClass->attachToClass
3,類有load 、分類無(wú)load
我們把分類ZYPerson(ZYA)里面的load方法屏蔽保留類ZYPerson里的load。其他環(huán)境不變。然后運(yùn)行我們的代碼。結(jié)果如下

類有
load、分類無(wú)load時(shí),分類中的方法在編譯階段已添加到ro中

類有
load,分類無(wú)load。不會(huì)走到attachCategories方法只會(huì)走類非懶加載流程:
_read_images 非懶加載類->realizeClassWithoutSwift->methodizeClass->attachToClass
4,類無(wú)load 、分類無(wú)load
我們把分類ZYPerson(ZYA)和類ZYPerson里的load都屏蔽。其他環(huán)境不變。然后運(yùn)行我們的代碼。結(jié)果如下

這次我們發(fā)現(xiàn)是先走到了main.m文件的main函數(shù)里斷點(diǎn)到了ZYPerson類的alloc方法,在這之前什么打印都沒(méi)有,然后我讓斷點(diǎn)往下走一步就出現(xiàn)了上圖的步驟先到realizeClassWithoutSwift打印ro沒(méi)有發(fā)現(xiàn)分類方法,再往下走一步斷點(diǎn)到了methodizeClass方法打印ro發(fā)現(xiàn)了分類方法已經(jīng)添加進(jìn)去了。
類無(wú)
load、分類無(wú)load時(shí),分類中的方法在類第一次發(fā)送消息后會(huì)重新走類加載流程到methodizeClass方法才會(huì)加載進(jìn)ro。

類有
load,分類無(wú)load。不會(huì)走到attachCategories方法。在類沒(méi)有發(fā)消息前我們打印的流程方法一個(gè)都不走。但是如果讓它alloc成功也就是發(fā)送第一次消息后就直接走了非懶加載類流程了。
多個(gè)分類的情況,如我們創(chuàng)建三個(gè)ZYPerson 的分類ZYPerson(ZYA)、ZYPerson(ZYB)、``ZYPerson(ZYC)。
因?yàn)榉诸愄噙@里就不把具體的沒(méi)種情況分析過(guò)程列出來(lái)了,我直接貼結(jié)果:
1,類有load,多個(gè)分類都有load:
類有
load和多個(gè)分類都有load,調(diào)用attachCategories方法初始化分類。
2,類有load,多個(gè)分類都無(wú)load
分類中的方法在編譯階段已添加到
data()中,不會(huì)調(diào)用attachCategories方法。
3,類有load,多個(gè)分類部分實(shí)現(xiàn)load方法
類有
load和多個(gè)分類部分實(shí)現(xiàn)load方法,調(diào)用attachCategories方法初始化分類。
4,類無(wú) load,多個(gè)分類都無(wú)load:
類無(wú)
load,多個(gè)分類都無(wú)load,第一次消息發(fā)送時(shí)初始化,并且分類中的方法自動(dòng)添加到data()中。
5,類無(wú) load,多個(gè)分類部分實(shí)現(xiàn)load方法:
類無(wú)
load,多個(gè)分類超過(guò)一個(gè)實(shí)現(xiàn)load,會(huì)走attachCategories方法初始化分類。如果只是一個(gè)分類實(shí)現(xiàn)load,那么分類中的方法在編譯階段已添加到data()中,不會(huì)調(diào)用attachCategories方法。
分析attachCategories方法:
上面我們探索了attachCategories的調(diào)用流程和條件。下面我們查看下attachCategories到底做了什么操作。
attachCategories分析 :
// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
{
if (slowpath(PrintReplacedMethods)) {
printReplacements(cls, cats_list, cats_count);
}
if (slowpath(PrintConnecting)) {
_objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
}
/*
* Only a few classes have more than 64 categories during launch.
* This uses a little stack, and avoids malloc.
*
* Categories must be added in the proper order, which is back
* to front. To do that with the chunking, we iterate cats_list
* from front to back, build up the local buffers backwards,
* and call attachLists on the chunks. attachLists prepends the
* lists, so the final result is in the expected order.
*/
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
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();
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[I];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
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();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
從上面的代碼分析我們可以把這個(gè)方法分為三個(gè)部分。
第一部分:前期預(yù)備工作
constexpr uint32_t ATTACH_BUFSIZ = 64;
method_list_t *mlists[ATTACH_BUFSIZ];
property_list_t *proplists[ATTACH_BUFSIZ];
protocol_list_t *protolists[ATTACH_BUFSIZ];
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();
這里創(chuàng)建了三個(gè)數(shù)組,分別是方法數(shù)組mlists、屬性數(shù)組proplists、協(xié)議數(shù)組protolists,并且每個(gè)數(shù)組的大小都是64 ATTACH_BUFSIZ = 64;
然后就去根據(jù)傳入的參數(shù)flags 判斷是否是元類。并且去獲取rwe。
rwe的創(chuàng)建處理:
auto rwe = cls->data()->extAllocIfNeeded();
以上代碼我們進(jìn)行以下跟蹤(文章開(kāi)頭有跟蹤但是并不完全),看看文章開(kāi)頭一直在已獲得rwe到底是什么時(shí)候存在的。command+點(diǎn)擊跟蹤extAllocIfNeeded()




也就是在這里,開(kāi)始創(chuàng)建
rwe,并且第一次調(diào)用了attachLists方法,進(jìn)行方法處理,這個(gè)方法在下面的步驟是重頭戲
第二部分:
for (uint32_t i = 0; i < cats_count; i++) {
auto& entry = cats_list[I];
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
if (propcount == ATTACH_BUFSIZ) {
rwe->properties.attachLists(proplists, propcount);
propcount = 0;
}
proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
}
protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
if (protolist) {
if (protocount == ATTACH_BUFSIZ) {
rwe->protocols.attachLists(protolists, protocount);
protocount = 0;
}
protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
}
}
這部分主要是針對(duì)方法、屬性、協(xié)議三個(gè)數(shù)組進(jìn)行獲取值和響應(yīng)處理。這里我們只看方法數(shù)組的處理。
在這里我們看到有區(qū)分元類和非元類的處理:
讀取方法數(shù)組:
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
methodsForMeta:
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
如果是元類就返回類方法,如果非元類就返回實(shí)例方法。
if (mlist) {
if (mcount == ATTACH_BUFSIZ) {
prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
rwe->methods.attachLists(mlists, mcount);
mcount = 0;
}
mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
fromBundle |= entry.hi->isBundle();
}
我們?cè)谶@個(gè)for循環(huán)前開(kāi)啟我們之前添加的特殊打印方法,確保我們的ZYPerson(ZYA)分類進(jìn)來(lái)后我們進(jìn)行精準(zhǔn)分析。我們?cè)诖蛴〈a打上斷點(diǎn),使流程進(jìn)入我們的方法處理for循環(huán),然后讓for循環(huán)跑一次,這個(gè)時(shí)候我們看看處理的第一個(gè)方法,是怎么添加到數(shù)組的。如圖



其實(shí)在這里如果我們有多個(gè)分類比如上面分析的三個(gè)分類的時(shí)候可以在這里打印全部的分類信息,然后查看添加數(shù)組情況,會(huì)更直觀,我在這里沒(méi)有截圖,大家可以自己去打印試試。
mlist是個(gè)數(shù)組指針,里面存的是分類的方法,而mlists是個(gè)數(shù)組二維指針,他把mlist倒敘插入到數(shù)組第63位(最后位)。
第三部分:處理前面添加的方法數(shù)組mlists
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();
});
}
}
rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
分析:這部分因?yàn)閙count在前面自動(dòng)mcount ++了,所以此處大于0,進(jìn)入if條件。第一行代碼prepareMethodLists就是方法數(shù)組排序。(mlists + ATTACH_BUFSIZ - mcount 這個(gè)參數(shù)就是利用的地址平移操作)
然后就對(duì)上面獲取/創(chuàng)建(前面有探究如果存在就獲取不存在就創(chuàng)建)的rwe進(jìn)行調(diào)用方法attachLists添加mlists操作。
是主要流程都是去調(diào)用了一個(gè)attachLists方法。那我們就去看看這個(gè)attachLists方法到底做了什么
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();
}
}
我們?cè)谏鲜龇椒〝帱c(diǎn)跟蹤打?。?/p>
第一步:創(chuàng)建數(shù)組array(),并且將array()從一維數(shù)組變成二維指針數(shù)組

從上圖我們可知,第一次進(jìn)入該方法直接進(jìn)入eles,進(jìn)行array()初始化和設(shè)置大小,然后再里面存放本類方法列表數(shù)組指針在末位置。

最后for循環(huán)添加分類方法列表數(shù)組指針到數(shù)組最前面。所以array()里存的的都是指針,然而這些指針又是指向一個(gè)個(gè)method_list_t。
第二步:將上一步的array()進(jìn)行再次處理,從二維指針數(shù)組變成多維指針數(shù)組。如圖

第三步:第一次分類創(chuàng)建rw的時(shí)候就會(huì)進(jìn)入到這里,對(duì)本類的方法進(jìn)行處理,進(jìn)入點(diǎn)在attachCategories方法的auto rwe = cls->data()->extAllocIfNeeded();

補(bǔ)充
解析ro、rw、rwe?
我們從WWDC2020關(guān)于類的介紹視頻中,我們可以知道:
ro:是指clean memory,是直接從磁盤(pán)獲取的,權(quán)限是只讀的,所以不會(huì)被外界的操作影響。
rw:是指dirty memory,是從ro復(fù)制一份過(guò)來(lái)的,權(quán)限是可讀可寫(xiě)的,在運(yùn)行時(shí)過(guò)程中可以通過(guò)添加分類、調(diào)用運(yùn)行時(shí)添加方法等方式來(lái)動(dòng)態(tài)添加方法、“屬性"等內(nèi)容。rw存在的意義是為了適應(yīng)運(yùn)行時(shí)的功能使得我們開(kāi)發(fā)過(guò)程中動(dòng)態(tài)添加的東西可以被使用。但是我們不能一直在這個(gè)里面添加,因?yàn)橐曨l里說(shuō)這部分是比較"昂貴"的,因?yàn)槭謾C(jī)升級(jí)內(nèi)存費(fèi)用是非常高的,但是mac 卻可以使用,因?yàn)?code>mac本身支持的內(nèi)存大小就要比手機(jī)大。為了解決這個(gè)無(wú)限增大rw內(nèi)存的問(wèn)題,引出了下面的rwe。因?yàn)槲覀冏罾硐氲臓顟B(tài)是我們使用過(guò)程中對(duì)rw使用最好能像ro一樣,需要的東西直接獲取就好,不會(huì)被添加和的新東西污染。所以說(shuō)clean memory越多越好,在這樣的一個(gè)希望中就誕生了rwe。
rwe:rw的拓展,也被標(biāo)志為ext。目的是為了對(duì)那些可以在動(dòng)態(tài)添加的內(nèi)容上標(biāo)記、管理。減少這些在運(yùn)行時(shí)可變的東西污染到rw的數(shù)據(jù)。做了一個(gè)隔離的工作。
至此,文章告一段落,原創(chuàng)碼字不易,如能給您帶來(lái)些許啟發(fā)那也是給作者的極大鼓勵(lì)。也盼望需要轉(zhuǎn)載的朋友請(qǐng)標(biāo)注出處,謝謝!
遇事不決,可問(wèn)春風(fēng)。站在巨人的肩膀上學(xué)習(xí),如有疏忽或者錯(cuò)誤的地方還請(qǐng)多多指教。