在上一篇文章类加载原理(中)我们探索了非懒加载类
的加载原理、懒加载类
的加载原理以及分类
的一些前期探索。这篇文章我们继续看看分类的加载和实现。
分类探索前言
在上一篇文章我们在methodizeClass
方法里,进入prepareMethodLists
方法处理方法后发现并不会走if (rwe) rwe->methods.attachLists(&list, 1);
因为这个时候rwe
为NULL
。那我们就要去查看下这个rwe
是什么情况下被赋值呢?我们直接command+点击
跳转查看发现就在本方法的开头里赋值。
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();
//省略下面代码
}
从这里我们发现是从rw->ext()
获取值的,那我们就进一步跟踪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));
}
}
我们从上面的代码发现是个class_rw_ext_t
类型,并且发现了他的内存开辟方法extAllocIfNeeded
(就在class_rw_ext_t *ext() const
下方)。并且在这个方法里直接去利用get_ro_or_rwe
方法就获取到了rwe
,如果不行就直接调用extAlloc
方法alloc
一个。那我们继续跟踪下extAllocIfNeeded
这个方法,看在哪儿调用了。我们全局搜索:
经过查看图中搜索到的地方,排除第一个上面的方法实现。其他的分别在:
attachCategories
: 添加分类方法
objc_class::demangledName
: demangledName
方法在处理future
类的时候
class_setVersion
: 类版本设置方法
addMethods_finish
:添加方法结束
class_addProtocol
: 类添加协议方法
_class_addProperty
: 类添加属性方法
objc_duplicateClass
: 处理重复类方法
到这里我们发现太多方法了,那我们继续看methodizeClass
方法下面的代码看看能不能找到一些线索,到最后我们看到attachToClass
方法,跟踪进去发现最后也是调用了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);
//省略下面代码
}
在这个方法里有三处调用,前两处收到变量previously
的影响,而这个变量又是从方法里的参数直接传进来的。所以我们去搜索methodizeClass
方法看看在哪里有传这个previously
参数。发现是在方法realizeClassWithoutSwift
里直接传进来的,而这个参数又是从realizeClassWithoutSwift
方法作为参数传进来的,所以我们继续反向查找溯源realizeClassWithoutSwift
。搜索结果我们发现realizeClassWithoutSwift
方法调用处previously
参数都是传的nil
。所以我们基本可以判定前面if
不会走。我们只需要看最后一个objc::unattachedCategories.attachToClass
方法调用处。
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);
/* *********************尝试打印看看我们自己的类能不能进来*****************/
bool isMeta = (flags & ATTACH_METACLASS);
const char *mangledName = cls->nonlazyMangledName();
//定义自己的类名
const char *ZYPersonName = "ZYPerson";
//比较自己的类名和读取的是否一致一致就进入if
if (strcasecmp(ZYPersonName, mangledName) == 0) {
if (!isMeta) {
printf("ZY 我们需要的信息: %s - - %s\n",__func__,mangledName);
}
}
/* *********************尝试打印看看我们自己的类能不能进来*****************/
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);
}
}
分析:从上面这些方法发现都是在运行时才调用的方法,以及一些注释我们还发现很多都有用到* fixme
来描述,也就是说这个rwe
的创建是放在运行时进行动态处理的,这样符合了WWDC2020
里关于类的变动和分析的说法。从上面的方法分析我们可以知道只有添加分类最符合我们的要求,因为从WWDC2020
视频里讲的在添加分类、动态添加方法等地方会有这个rwe
,并且我们在methodizeClass
方法最后也是引导我们走了attachToClass
并且在这个方法最终是调用方法attachCategories
,而分类我们从来没有探索过,也不知道它在什么时候加载处理的,所以我们去追踪分类这条线,
类和分类搭配加载
上面我们分析了attachCategories
这个方法的一些内容处理,下面我们来探索下attachCategories
加载的流程。我们仍然全局搜索attachCategories
看看它在什么时候被调用:
从上面的全局搜索我们可以确定调用的地方有两个:
attachToClass
: 添加分类到类
load_categories_nolock
: 加载分类
前期分析和预备工作
到这里我们不禁要思考我们利用这种方法调用追溯法是否合适,因为发现我们这种方法查找到的路有多条,而且我们不知道是否后面还有多分支调用上面这两个方法。如果用这种方法查找追溯会变得特别复杂,最理想的情况是跟踪一个方法一直只有一条线路。但现在显然不行,所以我们换一种方法 我们直接去上面两个方法和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);
/* *********************尝试打印看看我们自己的类能不能进来*****************/
bool isMeta = (flags & ATTACH_METACLASS);
const char *mangledName = cls->nonlazyMangledName();
//定义自己的类名
const char *ZYPersonName = "ZYPerson";
//比较自己的类名和读取的是否一致一致就进入if
if (strcasecmp(ZYPersonName, mangledName) == 0) {
if (!isMeta) {
printf("ZY 我们需要的信息: %s - - %s\n",__func__,mangledName);
}
}
/* *********************尝试打印看看我们自己的类能不能进来*****************/
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};
/* *********************尝试打印看看我们自己的类能不能进来*****************/
const char *mangledName = cls->nonlazyMangledName();
//定义自己的类名
const char *ZYPersonName = "ZYPerson";
//比较自己的类名和读取的是否一致一致就进入if
if (strcasecmp(ZYPersonName, mangledName) == 0) {
printf("ZY 我们需要的信息:load_categories_nolock %s - - %s\n",__func__,mangledName);
}
/* *********************尝试打印看看我们自己的类能不能进来*****************/
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();
/* *********************尝试打印看看我们自己的类能不能进来*****************/
const char *mangledName = cls->nonlazyMangledName();
//定义自己的类名
const char *ZYPersonName = "ZYPerson";
//比较自己的类名和读取的是否一致一致就进入if
if (strcasecmp(ZYPersonName, mangledName) == 0) {
if (!isMeta) {
printf("attachCategories - ZY 我们需要跟踪的信息: %s - - %s\n",__func__,mangledName);
}
}
/* *********************尝试打印看看我们自己的类能不能进来*****************/
//省略下面代码
}
然后我们结合前面文章类的加载步骤我们都在特殊打印的地方添加断点,比如以下方法:
realizeClassWithoutSwift
methodizeClass
load_categories_nolock
attachCategories
然后创建一个类和一个分类来测试:
我们创建一个ZYPerson
的分类ZYPerson (ZYA)
。因为我们在前面探究类的加载的时候发现添加load{}
方法会在类发消息前就加载了,所以我们为了保证分类加载进行我们也给分类添加上load{}
。然后在main.m
文件里调用分类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
而我们在前面的打断点方法里打印查找ro
或者class
信息是否包含了分类的方法。如下图:
类有
load
、分类也有load
时,分类在load_categories_nolock
方法里才会加载到ro
里。
我们在attachCategories
方法断点的地方利用lldb
调试bt
命令查看堆栈的方法查看什么时候通过什么路径调用了我们的attachCategories
方法。
_read_images
非懒加载类打印;
realizeClassWithoutSwift
方法打印;
methodizeClass
方法打印;
以及上面我们分析的attachToClass
;
load_categories_nolock
方法打印。
最后我们到attachCategories
打印里去bt
打印堆栈信息。 bt
结果如下:
从上面的打印来和
bt
结果来看上面我们这种类和分类的搭配走的方法流程是:_read_images 非懒加载类
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
在这个地方转折调用了dyld
然后走
load_images
->loadAllCategories
->load_categories_nolock
->attachCategories
2,类无load
、分类有load
我们把ZYPerson
类里面的load
方法屏蔽保留分类ZYPerson(ZYA)
里的load
。其他环境不变。然后运行我们的代码。结果如下:
类无
load
、分类有load
时,分类中的方法在编译阶段已添加到ro
中
类无
load
,分类有load
。不会走到attachCategories
方法只会走类非懒加载流程:(也许这就是被迫营业了吧)
_read_images 非懒加载类
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
3,类有load
、分类无load
我们把分类ZYPerson(ZYA)
里面的load
方法屏蔽保留类ZYPerson
里的load
。其他环境不变。然后运行我们的代码。结果如下
类有
load
、分类无load
时,分类中的方法在编译阶段已添加到ro
中
类有
load
,分类无load
。不会走到attachCategories
方法只会走类非懒加载流程:
_read_images 非懒加载类
->realizeClassWithoutSwift
->methodizeClass
->attachToClass
4,类无load
、分类无load
我们把分类ZYPerson(ZYA)
和类ZYPerson
里的load
都屏蔽。其他环境不变。然后运行我们的代码。结果如下
这次我们发现是先走到了main.m文件的main函数里断点到了ZYPerson类的alloc方法,在这之前什么打印都没有,然后我让断点往下走一步就出现了上图的步骤先到realizeClassWithoutSwift
打印ro没有发现分类方法,再往下走一步断点到了methodizeClass
方法打印ro
发现了分类方法已经添加进去了。
类无
load
、分类无load
时,分类中的方法在类第一次发送消息后会重新走类加载流程到methodizeClass
方法才会加载进ro
。
类有
load
,分类无load
。不会走到attachCategories
方法。在类没有发消息前我们打印的流程方法一个都不走。但是如果让它alloc
成功也就是发送第一次消息后就直接走了非懒加载类流程了。
多个分类的情况,如我们创建三个ZYPerson
的分类ZYPerson(ZYA)
、ZYPerson(ZYB)
、``ZYPerson(ZYC)。
因为分类太多这里就不把具体的没种情况分析过程列出来了,我直接贴结果:
1,类有load
,多个分类都有load
:
类有
load
和多个分类都有load
,调用attachCategories
方法初始化分类。
2,类有load
,多个分类都无load
分类中的方法在编译阶段已添加到
data()
中,不会调用attachCategories
方法。
3,类有load
,多个分类部分实现load
方法
类有
load
和多个分类部分实现load
方法,调用attachCategories
方法初始化分类。
4,类无 load
,多个分类都无load
:
类无
load
,多个分类都无load
,第一次消息发送时初始化,并且分类中的方法自动添加到data()
中。
5,类无 load
,多个分类部分实现load
方法:
类无
load
,多个分类超过一个实现load
,会走attachCategories
方法初始化分类。如果只是一个分类实现load
,那么分类中的方法在编译阶段已添加到data()
中,不会调用attachCategories
方法。
分析attachCategories
方法:
上面我们探索了attachCategories
的调用流程和条件。下面我们查看下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);
}
从上面的代码分析我们可以把这个方法分为三个部分。
第一部分:前期预备工作
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();
这里创建了三个数组,分别是方法数组mlists
、属性数组proplists
、协议数组protolists
,并且每个数组的大小都是64
ATTACH_BUFSIZ = 64
;
然后就去根据传入的参数flags
判断是否是元类
。并且去获取rwe
。
rwe
的创建处理:
auto rwe = cls->data()->extAllocIfNeeded();
以上代码我们进行以下跟踪(文章开头有跟踪但是并不完全),看看文章开头一直在已获得rwe
到底是什么时候存在的。command+点击
跟踪extAllocIfNeeded()
也就是在这里,开始创建
rwe
,并且第一次调用了attachLists
方法,进行方法处理,这个方法在下面的步骤是重头戏
第二部分:
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;
}
}
这部分主要是针对方法、属性、协议三个数组进行获取值和响应处理。这里我们只看方法数组的处理。
在这里我们看到有区分元类和非元类的处理:
读取方法数组:
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
methodsForMeta
:
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
如果是元类就返回类方法,如果非元类就返回实例方法。
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();
}
我们在这个for循环
前开启我们之前添加的特殊打印方法,确保我们的ZYPerson(ZYA)
分类进来后我们进行精准分析。我们在打印代码打上断点,使流程进入我们的方法处理for循环
,然后让for循环
跑一次,这个时候我们看看处理的第一个方法,是怎么添加到数组的。如图
其实在这里如果我们有多个分类比如上面分析的三个分类的时候可以在这里打印全部的分类信息,然后查看添加数组情况,会更直观,我在这里没有截图,大家可以自己去打印试试。
mlist
是个数组指针
,里面存的是分类的方法,而mlists
是个数组二维指针
,他把mlist
倒叙插入到数组第63
位(最后位)。
第三部分:处理前面添加的方法数组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);
分析:这部分因为mcount在前面自动mcount ++
了,所以此处大于0
,进入if条件。第一行代码prepareMethodLists
就是方法数组排序。(mlists + ATTACH_BUFSIZ - mcount
这个参数就是利用的地址平移操作)
然后就对上面获取/创建(前面有探究如果存在就获取不存在就创建)的rwe
进行调用方法attachLists
添加mlists
操作。
是主要流程都是去调用了一个attachLists
方法。那我们就去看看这个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();
}
}
我们在上述方法断点跟踪打印:
第一步:创建数组array()
,并且将array()
从一维数组变成二维指针数组
从上图我们可知,第一次进入该方法直接进入eles,进行array()
初始化和设置大小,然后再里面存放本类方法列表数组指针在末位置。
最后for循环添加分类方法列表数组指针到数组最前面。所以array()里存的的都是指针,然而这些指针又是指向一个个method_list_t
。
第二步:将上一步的array()
进行再次处理,从二维指针数组变成多维指针数组。如图
第三步:第一次分类创建rw
的时候就会进入到这里,对本类的方法进行处理,进入点在attachCategories
方法的auto rwe = cls->data()->extAllocIfNeeded();
补充
解析ro
、rw
、rwe
?
我们从WWDC2020
关于类的介绍视频中,我们可以知道:
ro
:是指clean memory
,是直接从磁盘获取的,权限是只读的,所以不会被外界的操作影响。
rw
:是指dirty memory
,是从ro
复制一份过来的,权限是可读可写的,在运行时过程中可以通过添加分类、调用运行时添加方法等方式来动态添加方法、“属性"等内容。rw
存在的意义是为了适应运行时的功能使得我们开发过程中动态添加的东西可以被使用。但是我们不能一直在这个里面添加,因为视频里说这部分是比较"昂贵"的,因为手机升级内存费用是非常高的,但是mac
却可以使用,因为mac
本身支持的内存大小就要比手机大。为了解决这个无限增大rw
内存的问题,引出了下面的rwe
。因为我们最理想的状态是我们使用过程中对rw
使用最好能像ro
一样,需要的东西直接获取就好,不会被添加和的新东西污染。所以说clean memory
越多越好,在这样的一个希望中就诞生了rwe
。
rwe
:rw
的拓展,也被标志为ext
。目的是为了对那些可以在动态添加的内容上标记、管理。减少这些在运行时可变的东西污染到rw
的数据。做了一个隔离的工作。
至此,文章告一段落,原创码字不易,如能给您带来些许启发那也是给作者的极大鼓励。也盼望需要转载的朋友请标注出处,谢谢!
遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。