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 bitstruct _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函数的调用者:
attachToClassload_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函数所调用
voidload_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 listsuint32_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 listlist = addedLists[0];validate();}else {// 1 list -> many listsPtr<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结构体
