1. 分类的本质
创建LGPerson
的LG
分类
@interface LGPerson (LG) <NSObject>
@property (nonatomic, copy) NSString *c_name;
- (void)c_say66;
+ (void)say99;
@end
@implementation LGPerson (LG)
- (void)c_say66{
NSLog(@"%@ - %s",self , __func__);
}
+ (void)say99{
NSLog(@"%@ - %s",self , __func__);
}
@end
1.1 cpp
文件探索
转为cpp
文件
clang -rewrite-objc main.m -o main.cpp
打开cpp文件,搜索LGPerson_$_LG
static struct _category_t _OBJC_$_CATEGORY_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"LGPerson",
0, // &OBJC_CLASS_$_LGPerson,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_LG,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_LGPerson_$_LG,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LGPerson_$_LG,
};
- 分类被转为
category_t
结构体,设置的分类名称和主类信息分别为LGPerson
和0
,这些显然是错误的数据。因为分类是运行时加载,此时编译阶段,设置的数据只是临时占位
category_t
结构体
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
name
:分类名称cls
:主类instance_methods
:实例方法列表class_methods
:类方法列表protocols
:协议列表properties
:属性列表
分类没有所属的元类,所以实例方法和类方法都存储在分类中,分别存储在instance_methods
和class_methods
两个列表中
实例方法
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"c_say66", "v16@0:8", (void *)_I_LGPerson_LG_c_say66}}
};
- 包含实例方法
c_say66
,但没有生成c_name
属性的getter/setter
方法,所以分类中需要使用关联对象的方式实现成员变量的效果
类方法
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_CLASS_METHODS_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"say99", "v16@0:8", (void *)_C_LGPerson_LG_say99}}
};
- 包含类方法
say99
协议列表
static struct /*_protocol_list_t*/ {
long protocol_count; // Note, this is 32/64 bit
struct _protocol_t *super_protocols[1];
} _OBJC_CATEGORY_PROTOCOLS_$_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {
1,
&_OBJC_PROTOCOL_NSObject
};
- 包含协议
NSObject
属性
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"c_name","T@\"NSString\",C,N"}}
};
- 包含属性
c_name
1.2 objc
源码探索
打开objc
源码,搜索struct category_t
struct category_t {
const char *name;
classref_t cls;
WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
WrappedPtr<method_list_t, PtrauthStrip> classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
protocol_list_t *protocolsForMeta(bool isMeta) {
if (isMeta) return nullptr;
else return protocols;
}
};
- 源码中的
category_t
结构,和C++
代码中的略有差异 - 将属性分为对象属性和类属性,但对于类属性并不支持
2. rw & ro & rwe
2.1 什么是rw
、ro
和rwe
?
ro
属于clean memory
,在编译即确定的内存空间,只读,加载后不会改变内容的空间rw
属于dirty memory
,是运行时结构,可读可写,可以向类中添加属性、方法等,在运行时会改变的内存rwe
相当于类的额外信息,因为在实际使用过程中,只有很少的类会真正的改变他们的内容,所以为避免资源的消耗就有了rwe
2.2 类的结构里面为什么会有rw
和ro
以及rwe
?
rw
中包括ro
和rwe
◦ 类的属性和方法在运行时可进行更改,例如:使用category
或Runtime API
。因为ro
是只读的,所以需要在rw
中来跟踪这些东西
◦ 日常开发中,只有少部分类会被更改它们的属性和方法,为了让dirty memory
占用更少的空间,把rw
中可变的部分抽取出来为rwe
- 运行时,如果需要动态向类中添加方法协议等,会创建
rwe
,并将ro
的数据优先attache
到rwe
中。在读取时会优先返回rwe
的数据,如果rwe
没有被初始化,则返回ro
的数据 dirty memory
比clean memory
更昂贵,当系统物理内存紧张的时候,会回收掉clean memory
内存,所以clean memory
越多越好,dirty memory
越少越好
3. 指针强转数据结构
3.1 原理分析
在_read_images
函数中,【第九步】类的加载处理,调用realizeClassWithoutSwift
函数,通过cls
的data
函数,可强转为class_ro_t
结构体指针
auto ro = (const class_ro_t *)cls->data();
cls
的data
函数,内部调用bits
的data
函数
class_rw_t *data() const {
return bits.data();
}
bits
的data
函数,将位运算后的地址强转为class_rw_t
结构体指针
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
bits
是uintptr_t
指针类型,和FAST_DATA_MASK
进行&
操作,返回的还是一个地址指针。地址指针的特性,可对其进行取值、内存平移和类型强转等操作。而这里就使用到类型强转,将地址指针强转为class_rw_t
结构体指针,只要内存结构一致,即可正常解析
3.2 llvm
源码探索
打开llvm
源码,搜索class_ro_t
找到class_ro_t
结构体,其中Read
函数,读取地址,为结构体成员变量赋值
进入Read
函数
- 计算结构体占用的大小
- 读取内存地址
- 通过内存地址得到
extractor
- 使用
extractor
调用Get
函数得到指定成员变量的值 - 传入的
cursor
为偏移值,地址传递,在函数内部会对齐修改
调用Read
函数的时机,例如:lldb
通过一个地址,输出cls
的rw
、ro
结构
- 调用结构体
Read
函数,将地址中的值转换为结构体成员变量 - 返回转换结果是否成功
案例
class_getInstanceMethod
方法,返回Method
类型,而Method
类型的本质是method_t
结构体指针,我们可以定义内存结构相同的结构体,将其进行强转
定义自定义结构体z_objc_method
struct z_objc_method {
SEL _Nonnull method_name;
char * _Nullable method_types;
IMP _Nonnull method_imp;
};
在main
函数中,调用class_getInstanceMethod
方法,将其进行强转
Method m = class_getInstanceMethod(LGPerson.class, @selector(say666));
struct z_objc_method *ptr = (struct z_objc_method *)m;
-------------------------
(lldb) p *ptr
(z_objc_method) $0 = {
method_name = "say666"
method_types = 0x0000000100003f79 "v16@0:8"
method_imp = 0x0000000100003b10 (objc_test`-[LGPerson say666])
}
4. rwe的赋值
4.1 赋值流程
rwe
在class_rw_t
结构体中,使用extAllocIfNeeded
函数赋值
struct class_rw_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));
}
}
...
}
- 如果
rwe
不存在,调用extAlloc
函数进行开辟
extAllocIfNeeded
函数在动态添加/修改属性和方法时被调用,其中关键调用者为attachCategories
函数,它的作用是将分类中的方法列表、属性和协议附加到类中。这符合WWDC 2020
中的说法:“在运行时,如果需要动态向类中添加方法协议等,会创建rwe
,并将ro
的数据优先attache
到rwe
中”
4.2 attachCategories
反推思路
attachCategories
:将分类中的方法列表、属性和协议附加到类中
attachCategories
函数的调用者:
attachToClass
load_categories_nolock
:
4.2.1 attachToClass
函数
其中attachToClass
函数,只会在methodizeClass
函数中被调用,也就是读取cls
中的rw
、ro
、rwe
时
在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
的条件影响,如果previously
为真才会调用
previously
为函数参数,我们需要找到methodizeClass
函数的调用者,才能找到previously
传入的值
搜索methodizeClass
函数,它被realizeClassWithoutSwift
所调用,而previously
来自于realizeClassWithoutSwift
函数的参数,需要找到realizeClassWithoutSwift
函数的调用者
在源码中,所有调用realizeClassWithoutSwift
函数的地方,传入的previously
的值都是nil
,所有previously
可能是预留参数,暂时未使用到
所以attachToClass
函数,只会在methodizeClass
中有一处调用时机
我们得到attachCategories
的流程之一,在类的加载过程中,直接被调用
流程一:realizeClassWithoutSwift
→methodizeClass
→attachToClass
→attachCategories
4.2.2 load_categories_nolock
函数
在源码中,搜索load_categories_nolock
,共有两处调用
第一处调用,在_read_images
函数中,【第八步】分类处理时调用
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
...
if (didInitialAttachCategories) {
for (EACH_HEADER) {
load_categories_nolock(hi);
}
}
...
}
第二处调用,在loadAllCategories
函数中调用
static void loadAllCategories() {
mutex_locker_t lock(runtimeLock);
for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
load_categories_nolock(hi);
}
}
而loadAllCategories
函数,被load_images
函数所调用
void
load_images(const char *path __unused, const struct mach_header *mh)
{
if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
didInitialAttachCategories = true;
loadAllCategories();
}
...
}
流程二:_read_images
→load_categories_nolock
→attachCategories
流程三:load_images
→loadAllCategories
→load_categories_nolock
→attachCategories
5. attachList算法
在attachCategories
中,先对方法进行排序,然后调用attachList
函数
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();
}
}
以方法为例,算法中分为零对一、一对多、多对对三种情况
5.1 零对一
如果list
不存在,进入零对一流程
list = addedLists[0];
validate();
addedLists
为主类的方法列表,获取addedLists
列表中的首个元素,赋值给list
。此时list
存储的是addedLists
列表首地址,类型为method_list_t
结构体指针
5.2 一对多
如果list
存在,但元素中不包含数组,进入一对多流程
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();
- 旧列表存在,即为
1
,否则为0
- 新列表元素总数 = 旧列表元素总数 + 追加元素总数
- 新列表开辟空间,并进行
setArray
操作,标记列表为数组 - 将旧列表元素写入新列表的结尾
- 遍历追加元素,依次存储到新列表中
addedLists
为分类的方法列表,获取addedLists
列表中的指定元素,存储在新列表的指定索引位置,元素的类型为method_list_t
结构体指针
5.3 多对多
如果list
中的标记为数组,进入多对多流程
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();
- 获取旧列表中的元素总数
- 新列表元素总数 = 旧列表元素总数 + 追加元素总数
- 新列表开辟空间
- 将旧列表中的元素,从结尾开始,依次从新列表的结尾处开始存储
- 遍历追加元素,依次存储到新列表中
- 将新列表进行
setArray
操作,标记列表为数组
addedLists
为分类的方法列表,元素的类型为method_list_t
结构体指针
在日常开发中,一个类可能会存在多个分类,此时的存储结构为[分类A
, 分类B
, 分类C
,主类]。分类A/B/C
的顺序和编译顺序有关,谁排在前面不一定
6. 分类加载流程
分类和主类都有各自的load
方法,对于load
方法的实现,会影响到主类与分类的加载流程:
- 非懒加载分类 + 非懒加载类
- 非懒加载分类 + 懒加载类
- 懒加载分类 + 非懒加载类
- 懒加载分类 + 懒加载类
- 多分类 + 主类
6.1 非懒加载分类 + 非懒加载类
当主类实现load
方法,分类也实现load
方法时
主类加载流程:map_images
→map_images_nolock
→_read_images
→realizeClassWithoutSwift
→methodizeClass
→attachToClass
分类加载流程:load_images
→loadAllCategories
→load_categories_nolock
→load_categories_nolock
→attachCategories
→attachLists
rwe
的赋值:分类加载流程的attachCategories
函数中,调用cls->data()->extAllocIfNeeded()
通过cls->data()
获取ro
的方法列表,只包含主类的方法,分类的方法在调用cls->data()->extAllocIfNeeded()
后,存储在rwe
中
6.2 非懒加载分类 + 懒加载类
主类未实现load
方法,但分类实现load
方法时
主类加载流程:map_images
→map_images_nolock
→_read_images
→realizeClassWithoutSwift
→methodizeClass
→attachToClass
分类加载流程:无
rwe
的赋值:无
主类为懒加载类,但由于分类实现load
方法,主类只能被迫营业。通过cls->data()
获取ro
的方法列表,包含了主类+分类中的所有方法。后续没有分类加载流程,也没有rwe
的赋值
所以这种情况下,分类的方法也是从MachO
中加载
6.3 懒加载分类 + 非懒加载类
主类实现load
方法,但分类未实现load
方法时
主类加载流程:map_images
→map_images_nolock
→_read_images
→realizeClassWithoutSwift
→methodizeClass
→attachToClass
分类加载流程:无
rwe
的赋值:无
通过cls->data()
获取ro
的方法列表,包含了主类+分类中的所有方法。后续没有分类加载流程,也没有rwe
的赋值
所以这种情况和非懒加载分类 + 懒加载类
的情况极为相似,分类的方法也是从MachO
中加载
6.4 懒加载分类 + 懒加载类
主类未实现load
方法,分类也未实现load
方法时
主类加载流程:lookUpImpOrForward
→initializeAndLeaveLocked
→initializeAndMaybeRelock
→realizeClassMaybeSwiftAndUnlock
→realizeClassMaybeSwiftMaybeRelock
→realizeClassWithoutSwift
→methodizeClass
→attachToClass
分类加载流程:无
rwe
的赋值:无
当主类和分类都未实现load
方法,主类会在运行时,首次消息发送时完成初始化。通过cls->data()
获取ro
的方法列表,包含了主类+分类中的所有方法。后续没有分类加载流程,也没有rwe
的赋值
所以这种情况下,分类的方法也是从MachO
中加载
6.5 多分类 + 主类
- 全部分类均为懒加载分类,未实现
load
方法,进入懒加载分类流程 - 主类为非懒加载类,即实现load方法。部分或全部分类为非懒加载分类,也实现
load
方法,进入非懒加载分类 + 非懒加载类
流程 - 主类为懒加载类,未实现
load
方法。仅一个分类实现load
方法,进入非懒加载分类 + 懒加载类
流程 - 主类为懒加载类,未实现
load
方法。超过一个分类实现load
方法,此时进入一个特殊流程
6.5.1 超过一个非懒加载分类 + 懒加载类
主类不会在_read_images
流程中初始化,而是在load_images
流程,进入方法加载方法流程
进入prepare_load_methods
函数,通过_getObjc2NonlazyCategoryList
,读取MachO
中__DATA
段__objc_nlcatlist
节,获取非懒加载的分类列表。循环分类列表,通过分类找到所属主类,对其进行初始化
如果一个主类有很多分类,循环时会进入realizeClassWithoutSwift
函数多次,但内部有判断条件,如果已经初始化,直接返回,所以不用担心主类会初始化多次
之后会进入熟悉的主类初始化流程:realizeClassWithoutSwift
→methodizeClass
→attachToClass
进入attachToClass
函数,找到主类中的所有分类。调用attachCategories
函数,进行分类的初始化
- 没有任何属性、方法的空分类,不会包含到列表中
主类加载流程:load_images
→prepare_load_methods
→realizeClassWithoutSwift
→methodizeClass
→attachToClass
分类加载流程:主类加载流程→attachCategories
rwe
的赋值:分类加载流程的attachCategories
函数中,调用cls->data()->extAllocIfNeeded()
6.6 分类方法的排序
非懒加载分类 + 非懒加载类
、超过一个非懒加载分类 + 懒加载类
两种情况,分类方法列表和主类方法列表分开存储,结构为:[分类A, 分类B, 分类C, 主类]
在分类或主类中,各自的方法列表已经进行地址升序。但不同的分类和主类之间,也会出现同名方法,此时如何保证查找到的方法,是排序在最前面分类中的方法呢?
在消息慢速查找流程中,调用search_method_list_inline
函数进行二分查找的外部,还有一个针对分类和主类的大循环,如果二分查找在分类中找到该方法,直接停止循环并返回IMP
。所以外部的循环,一定会保证找到的方法是排序在最前面分类中的方法
- 循环方式,即:内存平移
而其他几种情况,分类方法和主类方法存储在一个列表中,它们按照函数地址进行升序排序。使用二分查找寻找到该方法后,必须保证它是同名方法中排序在最前面的,所以内部还会进行遍历和--
操作,一直找到前面没有方法,或者前面的方法名称不同为止
7. attachList分析
当主类实现load
方法,分类也实现load
方法时,分类通过load_images
进行加载
流程:load_images
→loadAllCategories
→load_categories_nolock
→load_categories_nolock
→attachCategories
→attachLists
7.1 load_categories_nolock
在load_categories_nolock
函数中,循环获取分类,有多少分类,就会循环多少次
由于主类实现load
方法,在map_images
流程中已完成类的加载,所以进入cls->isRealized()
分支,调用attachCategories
函数
7.2 attachCategories
attachCategories
函数中,读取传入分类下的方法列表
将方法列表,存储到mlists
的结尾
调用prepareMethodLists
函数,对分类下的方法列表进行排序
调用rwe->methods.attachLists
,将分类中的方法插入到rwe
中
传入的方法列表为method_list_t
类型的二级指针
7.3 attachList
因为list
里存储的了主类的方法列表,进入一对多流程
通过setArray
开辟新列表的空间,array_t
结构体指针类型
将主类的方法列表,method_list_t
结构体指针存储到新列表的结尾
遍历分类的方法列表,通过addedLists[i]
,从二级指针中,获取method_list_t
结构体指针,存储到新列表的指定索引位置,保证分类的方法排在前面
最终存储结构:[分类方法列表, 主类方法列表]
无论是分类还是主类的方法列表,结构统一为method_list_t
结构体指针类型
对添加完分类方法列表后的rwe
进行打印
方法列表method_list_t
中存储的是指针地址,通过get
方法进行内存平移,获取索引对应的指针地址,而指针地址指向method_t
结构体
总结
分类的本质:
- 分类本质为
category_t
结构体,编译阶段,设置的数据只是临时占位 - 分类没有所属的元类,所以实例方法和类方法都存储在分类中
- 分类没有生成属性的
getter/setter
方法,所以分类中需要使用关联对象
什么是rw
、ro
和rwe
?
ro
属于clean memory
,在编译即确定的内存空间,只读,加载后不会改变内容的空间rw
属于dirty memory
,是运行时结构,可读可写,可以向类中添加属性、方法等,在运行时会改变的内存rwe
相当于类的额外信息,因为在实际使用过程中,只有很少的类会真正的改变他们的内容,所以为避免资源的消耗就有了rwe
类的结构里面为什么会有rw
和ro
以及rwe
?
rw
中包括ro
和rwe
◦ 类的属性和方法在运行时可进行更改,例如:使用category
或Runtime API
。因为ro
是只读的,所以需要在rw
中来跟踪这些东西
◦ 常开发中,只有少部分类会被更改它们的属性和方法,为了让dirty memory
占用更少的空间,把rw
中可变的部分抽取出来为rwe
- 运行时,如果需要动态向类中添加方法协议等,会创建
rwe
,并将ro
的数据优先attache
到rwe
中。在读取时会优先返回rwe
的数据,如果rwe
没有被初始化,则返回ro
的数据 dirty memory
比clean memory
更昂贵,当系统物理内存紧张的时候,会回收掉clean memory
内存,所以clean memory
越多越好,dirty memory
越少越好
指针强转数据结构:
- 地址指针的特性,可对其进行取值、内存平移和类型强转等操作
- 指针类型的强转,只要内存结构一致,即可正常解析
rwe
的赋值:
rwe
在使用category
或Runtime API
时,调用extAllocIfNeeded
函数赋值- 如果
rwe
不存在,调用extAlloc
函数进行开辟
attachCategories
反推思路:
- 流程一:
realizeClassWithoutSwift
→methodizeClass
→attachToClass
→attachCategories
- 流程二:
_read_images
→load_categories_nolock
→attachCategories
,触发方式未知 - 流程三:
load_images
→loadAllCategories
→load_categories_nolock
→attachCategories
attachList
算法:
- 如果
list
不存在,进入零对一流程 - 如果
list
存在,但元素中不包含数组,进入一对多流程
◦ 旧列表存在,即为1
,否则为0
◦ 新列表元素总数 = 旧列表元素总数 + 追加元素总数
◦ 新列表开辟空间,并进行setArray
操作,标记列表为数组
◦ 将旧列表元素写入新列表的结尾
◦ 遍历追加元素,依次存储到新列表中
- 如果
list
中的标记为数组,进入多对多流程
◦ 获取旧列表中的元素总数
◦ 新列表元素总数 = 旧列表元素总数 + 追加元素总数
◦ 新列表开辟空间
◦ 将旧列表中的元素,从结尾开始,依次从新列表的结尾处开始存储
◦ 遍历追加元素,依次存储到新列表中
◦ 将新列表进行setArray
操作,标记列表为数组
- 一个类可能会存在多个分类,此时的存储结构为[分类
A
, 分类B
, 分类C
,主类] 分类A/B/C
的顺序和编译顺序有关,谁排在前面不一定
非懒加载分类 + 非懒加载类
:
- 主类加载流程:
map_images
→map_images_nolock
→_read_images
→realizeClassWithoutSwift
→methodizeClass
→attachToClass
- 分类加载流程:
load_images
→loadAllCategories
→load_categories_nolock
→load_categories_nolock
→attachCategories
→attachLists
rwe
的赋值:分类加载流程的attachCategories
函数中,调用cls->data()->extAllocIfNeeded()
非懒加载分类 + 懒加载类
:
- 主类加载流程:
map_images
→map_images_nolock
→_read_images
→realizeClassWithoutSwift
→methodizeClass
→attachToClass
- 分类加载流程:无
rwe
的赋值:无
懒加载分类 + 非懒加载类
:
- 主类加载流程:
map_images
→map_images_nolock
→_read_images
→realizeClassWithoutSwift
→methodizeClass
→attachToClass
- 分类加载流程:无
rwe
的赋值:无
懒加载分类 + 懒加载类
:
- 主类加载流程:
lookUpImpOrForward
→initializeAndLeaveLocked
→initializeAndMaybeRelock
→realizeClassMaybeSwiftAndUnlock
→realizeClassMaybeSwiftMaybeRelock
→realizeClassWithoutSwift
→methodizeClass
→attachToClass
- 分类加载流程:无
rwe
的赋值:无
多分类 + 主类
:
- 全部分类均为懒加载分类,未实现
load
方法,进入懒加载分类流程 - 主类为非懒加载类,即实现
load
方法。部分或全部分类为非懒加载分类,也实现load
方法,进入非懒加载分类 + 非懒加载类
流程 - 主类为懒加载类,未实现
load
方法。仅一个分类实现load
方法,进入非懒加载分类 + 懒加载类
流程 - 主类为懒加载类,未实现
load
方法。超过一个分类实现load
方法,此时进入一个特殊流程
超过一个非懒加载分类 + 懒加载类
:
- 主类加载流程:
load_images
→prepare_load_methods
→realizeClassWithoutSwift
→methodizeClass
→attachToClass
- 分类加载流程:主类加载流程→
attachCategories
rwe
的赋值:分类加载流程的attachCategories
函数中,调用cls->data()->extAllocIfNeeded()
分类方法的排序:
非懒加载分类 + 非懒加载类
、超过一个非懒加载分类 + 懒加载类
两种情况:
◦ 结构为:[分类A
, 分类B
, 分类C
, 主类]
◦ 分类和主类之间的方法列表无需排序
◦ 查找时,依靠外部循环,找到的方法一定是排序在最前面分类中的方法
- 其他情况:
◦ 分类方法和主类方法存储在一个列表中
◦ 它们按照函数地址进行升序排序
◦ 使用二分查找,内部还会进行遍历和—操作,一直找到前面没有方法,或者前面的方法名称不同为止
attachList
分析:
- 以
非懒加载分类 + 非懒加载类
为例 load_categories_nolock
◦ 循环获取分类,有多少分类,就会循环多少次
attachCategories
◦ 读取传入分类下的方法列表
◦ 将方法列表,存储到mlists
的结尾
◦ 调用prepareMethodLists
函数,对分类下的方法列表进行排序
◦ 调用rwe->methods.attachLists
,将分类中的方法插入到rwe
中
◦ 传入的方法列表为method_list_t
类型的二级指针
attachList
◦ 因为list
里存储的了主类的方法列表,进入一对多流程
◦ 最终存储结构:[分类方法列表, 主类方法列表]
◦ 无论是分类还是主类的方法列表,结构统一为method_list_t
结构体指针类型
◦ 方法列表method_list_t
中存储的是指针地址,通过get
方法进行内存平移,获取索引对应的指针地址,而指针地址指向method_t
结构体