1. 分类的本质

创建LGPersonLG分类

  1. @interface LGPerson (LG) <NSObject>
  2. @property (nonatomic, copy) NSString *c_name;
  3. - (void)c_say66;
  4. + (void)say99;
  5. @end
  6. @implementation LGPerson (LG)
  7. - (void)c_say66{
  8. NSLog(@"%@ - %s",self , __func__);
  9. }
  10. + (void)say99{
  11. NSLog(@"%@ - %s",self , __func__);
  12. }
  13. @end

1.1 cpp文件探索

转为cpp文件

  1. clang -rewrite-objc main.m -o main.cpp

打开cpp文件,搜索LGPerson_$_LG

  1. static struct _category_t _OBJC_$_CATEGORY_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) =
  2. {
  3. "LGPerson",
  4. 0, // &OBJC_CLASS_$_LGPerson,
  5. (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_LG,
  6. (const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_LGPerson_$_LG,
  7. 0,
  8. (const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LGPerson_$_LG,
  9. };
  • 分类被转为category_t结构体,设置的分类名称和主类信息分别为LGPerson0,这些显然是错误的数据。因为分类是运行时加载,此时编译阶段,设置的数据只是临时占位

category_t结构体

  1. struct _category_t {
  2. const char *name;
  3. struct _class_t *cls;
  4. const struct _method_list_t *instance_methods;
  5. const struct _method_list_t *class_methods;
  6. const struct _protocol_list_t *protocols;
  7. const struct _prop_list_t *properties;
  8. };
  • name:分类名称
  • cls:主类
  • instance_methods:实例方法列表
  • class_methods:类方法列表
  • protocols:协议列表
  • properties:属性列表

分类没有所属的元类,所以实例方法和类方法都存储在分类中,分别存储在instance_methodsclass_methods两个列表中

实例方法

  1. static struct /*_method_list_t*/ {
  2. unsigned int entsize; // sizeof(struct _objc_method)
  3. unsigned int method_count;
  4. struct _objc_method method_list[1];
  5. } _OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {
  6. sizeof(_objc_method),
  7. 1,
  8. {{(struct objc_selector *)"c_say66", "v16@0:8", (void *)_I_LGPerson_LG_c_say66}}
  9. };
  • 包含实例方法c_say66,但没有生成c_name属性的getter/setter方法,所以分类中需要使用关联对象的方式实现成员变量的效果

类方法

  1. static struct /*_method_list_t*/ {
  2. unsigned int entsize; // sizeof(struct _objc_method)
  3. unsigned int method_count;
  4. struct _objc_method method_list[1];
  5. } _OBJC_$_CATEGORY_CLASS_METHODS_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {
  6. sizeof(_objc_method),
  7. 1,
  8. {{(struct objc_selector *)"say99", "v16@0:8", (void *)_C_LGPerson_LG_say99}}
  9. };
  • 包含类方法say99

协议列表

  1. static struct /*_protocol_list_t*/ {
  2. long protocol_count; // Note, this is 32/64 bit
  3. struct _protocol_t *super_protocols[1];
  4. } _OBJC_CATEGORY_PROTOCOLS_$_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {
  5. 1,
  6. &_OBJC_PROTOCOL_NSObject
  7. };
  • 包含协议NSObject

属性

  1. static struct /*_prop_list_t*/ {
  2. unsigned int entsize; // sizeof(struct _prop_t)
  3. unsigned int count_of_properties;
  4. struct _prop_t prop_list[1];
  5. } _OBJC_$_PROP_LIST_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {
  6. sizeof(_prop_t),
  7. 1,
  8. {{"c_name","T@\"NSString\",C,N"}}
  9. };
  • 包含属性c_name

1.2 objc源码探索

打开objc源码,搜索struct category_t

  1. struct category_t {
  2. const char *name;
  3. classref_t cls;
  4. WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
  5. WrappedPtr<method_list_t, PtrauthStrip> classMethods;
  6. struct protocol_list_t *protocols;
  7. struct property_list_t *instanceProperties;
  8. // Fields below this point are not always present on disk.
  9. struct property_list_t *_classProperties;
  10. method_list_t *methodsForMeta(bool isMeta) {
  11. if (isMeta) return classMethods;
  12. else return instanceMethods;
  13. }
  14. property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
  15. protocol_list_t *protocolsForMeta(bool isMeta) {
  16. if (isMeta) return nullptr;
  17. else return protocols;
  18. }
  19. };
  • 源码中的category_t结构,和C++代码中的略有差异
  • 将属性分为对象属性和类属性,但对于类属性并不支持

2. rw & ro & rwe

2.1 什么是rwrorwe?

  • ro属于clean memory,在编译即确定的内存空间,只读,加载后不会改变内容的空间
  • rw属于dirty memory,是运行时结构,可读可写,可以向类中添加属性、方法等,在运行时会改变的内存
  • rwe相当于类的额外信息,因为在实际使用过程中,只有很少的类会真正的改变他们的内容,所以为避免资源的消耗就有了rwe

2.2 类的结构里面为什么会有rwro以及rwe?

  • rw中包括rorwe

◦ 类的属性和方法在运行时可进行更改,例如:使用categoryRuntime API。因为ro是只读的,所以需要在rw中来跟踪这些东西
◦ 日常开发中,只有少部分类会被更改它们的属性和方法,为了让dirty memory占用更少的空间,把rw中可变的部分抽取出来为rwe

  • 运行时,如果需要动态向类中添加方法协议等,会创建rwe,并将ro的数据优先attacherwe中。在读取时会优先返回rwe的数据,如果rwe没有被初始化,则返回ro的数据
  • dirty memoryclean memory更昂贵,当系统物理内存紧张的时候,会回收掉clean memory内存,所以clean memory越多越好,dirty memory越少越好

3. 指针强转数据结构

3.1 原理分析

_read_images函数中,【第九步】类的加载处理,调用realizeClassWithoutSwift函数,通过clsdata函数,可强转为class_ro_t结构体指针

  1. auto ro = (const class_ro_t *)cls->data();

clsdata函数,内部调用bitsdata函数

  1. class_rw_t *data() const {
  2. return bits.data();
  3. }

bitsdata函数,将位运算后的地址强转为class_rw_t结构体指针

  1. class_rw_t* data() const {
  2. return (class_rw_t *)(bits & FAST_DATA_MASK);
  3. }

bitsuintptr_t指针类型,和FAST_DATA_MASK进行&操作,返回的还是一个地址指针。地址指针的特性,可对其进行取值、内存平移和类型强转等操作。而这里就使用到类型强转,将地址指针强转为class_rw_t结构体指针,只要内存结构一致,即可正常解析

3.2 llvm源码探索

打开llvm源码,搜索class_ro_t

找到class_ro_t结构体,其中Read函数,读取地址,为结构体成员变量赋值
image.png

进入Read函数
image.png

  • 计算结构体占用的大小
  • 读取内存地址

image.png

  • 通过内存地址得到extractor
  • 使用extractor调用Get函数得到指定成员变量的值
  • 传入的cursor为偏移值,地址传递,在函数内部会对齐修改

调用Read函数的时机,例如:lldb通过一个地址,输出clsrwro结构
image.png

  • 调用结构体Read函数,将地址中的值转换为结构体成员变量
  • 返回转换结果是否成功

案例

class_getInstanceMethod方法,返回Method类型,而Method类型的本质是method_t结构体指针,我们可以定义内存结构相同的结构体,将其进行强转

定义自定义结构体z_objc_method

  1. struct z_objc_method {
  2. SEL _Nonnull method_name;
  3. char * _Nullable method_types;
  4. IMP _Nonnull method_imp;
  5. };

main函数中,调用class_getInstanceMethod方法,将其进行强转

  1. Method m = class_getInstanceMethod(LGPerson.class, @selector(say666));
  2. struct z_objc_method *ptr = (struct z_objc_method *)m;
  3. -------------------------
  4. (lldb) p *ptr
  5. (z_objc_method) $0 = {
  6. method_name = "say666"
  7. method_types = 0x0000000100003f79 "v16@0:8"
  8. method_imp = 0x0000000100003b10 (objc_test`-[LGPerson say666])
  9. }

4. rwe的赋值

4.1 赋值流程

rweclass_rw_t结构体中,使用extAllocIfNeeded函数赋值

  1. struct class_rw_t {
  2. ...
  3. class_rw_ext_t *extAllocIfNeeded() {
  4. auto v = get_ro_or_rwe();
  5. if (fastpath(v.is<class_rw_ext_t *>())) {
  6. return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
  7. } else {
  8. return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
  9. }
  10. }
  11. ...
  12. }
  • 如果rwe不存在,调用extAlloc函数进行开辟

extAllocIfNeeded函数在动态添加/修改属性和方法时被调用,其中关键调用者为attachCategories函数,它的作用是将分类中的方法列表、属性和协议附加到类中。这符合WWDC 2020中的说法:“在运行时,如果需要动态向类中添加方法协议等,会创建rwe,并将ro的数据优先attacherwe中”

4.2 attachCategories反推思路

attachCategories:将分类中的方法列表、属性和协议附加到类中

attachCategories函数的调用者:

  • attachToClass
  • load_categories_nolock

4.2.1 attachToClass函数

其中attachToClass函数,只会在methodizeClass函数中被调用,也就是读取cls中的rwrorwe

methodizeClass函数中,共有三处调用

  1. static void methodizeClass(Class cls, Class previously)
  2. {
  3. ...
  4. // Attach categories.
  5. if (previously) {
  6. if (isMeta) {
  7. objc::unattachedCategories.attachToClass(cls, previously,
  8. ATTACH_METACLASS);
  9. } else {
  10. // When a class relocates, categories with class methods
  11. // may be registered on the class itself rather than on
  12. // the metaclass. Tell attachToClass to look for those.
  13. objc::unattachedCategories.attachToClass(cls, previously,
  14. ATTACH_CLASS_AND_METACLASS);
  15. }
  16. }
  17. objc::unattachedCategories.attachToClass(cls, cls,
  18. isMeta ? ATTACH_METACLASS : ATTACH_CLASS);
  19. ...
  20. }
  • 其中前两处调用,受到previously的条件影响,如果previously为真才会调用

previously为函数参数,我们需要找到methodizeClass函数的调用者,才能找到previously传入的值

搜索methodizeClass函数,它被realizeClassWithoutSwift所调用,而previously来自于realizeClassWithoutSwift函数的参数,需要找到realizeClassWithoutSwift函数的调用者

在源码中,所有调用realizeClassWithoutSwift函数的地方,传入的previously的值都是nil,所有previously可能是预留参数,暂时未使用到

所以attachToClass函数,只会在methodizeClass中有一处调用时机

我们得到attachCategories的流程之一,在类的加载过程中,直接被调用

流程一:realizeClassWithoutSwiftmethodizeClassattachToClassattachCategories

4.2.2 load_categories_nolock函数

在源码中,搜索load_categories_nolock,共有两处调用

第一处调用,在_read_images函数中,【第八步】分类处理时调用

  1. void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
  2. {
  3. ...
  4. if (didInitialAttachCategories) {
  5. for (EACH_HEADER) {
  6. load_categories_nolock(hi);
  7. }
  8. }
  9. ...
  10. }

第二处调用,在loadAllCategories函数中调用

  1. static void loadAllCategories() {
  2. mutex_locker_t lock(runtimeLock);
  3. for (auto *hi = FirstHeader; hi != NULL; hi = hi->getNext()) {
  4. load_categories_nolock(hi);
  5. }
  6. }

loadAllCategories函数,被load_images函数所调用

  1. void
  2. load_images(const char *path __unused, const struct mach_header *mh)
  3. {
  4. if (!didInitialAttachCategories && didCallDyldNotifyRegister) {
  5. didInitialAttachCategories = true;
  6. loadAllCategories();
  7. }
  8. ...
  9. }

流程二:_read_imagesload_categories_nolockattachCategories

流程三:load_imagesloadAllCategoriesload_categories_nolockattachCategories

5. attachList算法

attachCategories中,先对方法进行排序,然后调用attachList函数
image.png

attachLists函数,将类的方法、属性、协议,以相同算法存储到列表中

  1. void attachLists(List* const * addedLists, uint32_t addedCount) {
  2. if (addedCount == 0) return;
  3. if (hasArray()) {
  4. // many lists -> many lists
  5. uint32_t oldCount = array()->count;
  6. uint32_t newCount = oldCount + addedCount;
  7. array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
  8. newArray->count = newCount;
  9. array()->count = newCount;
  10. for (int i = oldCount - 1; i >= 0; i--)
  11. newArray->lists[i + addedCount] = array()->lists[i];
  12. for (unsigned i = 0; i < addedCount; i++)
  13. newArray->lists[i] = addedLists[i];
  14. free(array());
  15. setArray(newArray);
  16. validate();
  17. }
  18. else if (!list && addedCount == 1) {
  19. // 0 lists -> 1 list
  20. list = addedLists[0];
  21. validate();
  22. }
  23. else {
  24. // 1 list -> many lists
  25. Ptr<List> oldList = list;
  26. uint32_t oldCount = oldList ? 1 : 0;
  27. uint32_t newCount = oldCount + addedCount;
  28. setArray((array_t *)malloc(array_t::byteSize(newCount)));
  29. array()->count = newCount;
  30. if (oldList) array()->lists[addedCount] = oldList;
  31. for (unsigned i = 0; i < addedCount; i++)
  32. array()->lists[i] = addedLists[i];
  33. validate();
  34. }
  35. }

以方法为例,算法中分为零对一、一对多、多对对三种情况

5.1 零对一

如果list不存在,进入零对一流程

  1. list = addedLists[0];
  2. validate();

addedLists为主类的方法列表,获取addedLists列表中的首个元素,赋值给list。此时list存储的是addedLists列表首地址,类型为method_list_t结构体指针
image.png

5.2 一对多

如果list存在,但元素中不包含数组,进入一对多流程

  1. Ptr<List> oldList = list;
  2. uint32_t oldCount = oldList ? 1 : 0;
  3. uint32_t newCount = oldCount + addedCount;
  4. setArray((array_t *)malloc(array_t::byteSize(newCount)));
  5. array()->count = newCount;
  6. if (oldList) array()->lists[addedCount] = oldList;
  7. for (unsigned i = 0; i < addedCount; i++)
  8. array()->lists[i] = addedLists[i];
  9. validate();
  • 旧列表存在,即为1,否则为0
  • 新列表元素总数 = 旧列表元素总数 + 追加元素总数
  • 新列表开辟空间,并进行setArray操作,标记列表为数组
  • 将旧列表元素写入新列表的结尾
  • 遍历追加元素,依次存储到新列表中

addedLists为分类的方法列表,获取addedLists列表中的指定元素,存储在新列表的指定索引位置,元素的类型为method_list_t结构体指针
image.png

5.3 多对多

如果list中的标记为数组,进入多对多流程

  1. uint32_t oldCount = array()->count;
  2. uint32_t newCount = oldCount + addedCount;
  3. array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
  4. newArray->count = newCount;
  5. array()->count = newCount;
  6. for (int i = oldCount - 1; i >= 0; i--)
  7. newArray->lists[i + addedCount] = array()->lists[i];
  8. for (unsigned i = 0; i < addedCount; i++)
  9. newArray->lists[i] = addedLists[i];
  10. free(array());
  11. setArray(newArray);
  12. validate();
  • 获取旧列表中的元素总数
  • 新列表元素总数 = 旧列表元素总数 + 追加元素总数
  • 新列表开辟空间
  • 将旧列表中的元素,从结尾开始,依次从新列表的结尾处开始存储
  • 遍历追加元素,依次存储到新列表中
  • 将新列表进行setArray操作,标记列表为数组

addedLists为分类的方法列表,元素的类型为method_list_t结构体指针
image.png

在日常开发中,一个类可能会存在多个分类,此时的存储结构为[分类A, 分类B, 分类C,主类]。分类A/B/C的顺序和编译顺序有关,谁排在前面不一定

6. 分类加载流程

分类和主类都有各自的load方法,对于load方法的实现,会影响到主类与分类的加载流程:

  • 非懒加载分类 + 非懒加载类
  • 非懒加载分类 + 懒加载类
  • 懒加载分类 + 非懒加载类
  • 懒加载分类 + 懒加载类
  • 多分类 + 主类

6.1 非懒加载分类 + 非懒加载类

当主类实现load方法,分类也实现load方法时

主类加载流程:map_imagesmap_images_nolock_read_imagesrealizeClassWithoutSwiftmethodizeClassattachToClass

分类加载流程:load_imagesloadAllCategoriesload_categories_nolockload_categories_nolockattachCategoriesattachLists

rwe的赋值:分类加载流程的attachCategories函数中,调用cls->data()->extAllocIfNeeded()

通过cls->data()获取ro的方法列表,只包含主类的方法,分类的方法在调用cls->data()->extAllocIfNeeded()后,存储在rwe

6.2 非懒加载分类 + 懒加载类

主类未实现load方法,但分类实现load方法时

主类加载流程:map_imagesmap_images_nolock_read_imagesrealizeClassWithoutSwiftmethodizeClassattachToClass

分类加载流程:无

rwe的赋值:无

主类为懒加载类,但由于分类实现load方法,主类只能被迫营业。通过cls->data()获取ro的方法列表,包含了主类+分类中的所有方法。后续没有分类加载流程,也没有rwe的赋值

所以这种情况下,分类的方法也是从MachO中加载

6.3 懒加载分类 + 非懒加载类

主类实现load方法,但分类未实现load方法时

主类加载流程:map_imagesmap_images_nolock_read_imagesrealizeClassWithoutSwiftmethodizeClassattachToClass

分类加载流程:无

rwe的赋值:无

通过cls->data()获取ro的方法列表,包含了主类+分类中的所有方法。后续没有分类加载流程,也没有rwe的赋值

所以这种情况和非懒加载分类 + 懒加载类的情况极为相似,分类的方法也是从MachO中加载

6.4 懒加载分类 + 懒加载类

主类未实现load方法,分类也未实现load方法时

主类加载流程:lookUpImpOrForwardinitializeAndLeaveLockedinitializeAndMaybeRelockrealizeClassMaybeSwiftAndUnlockrealizeClassMaybeSwiftMaybeRelockrealizeClassWithoutSwiftmethodizeClassattachToClass

分类加载流程:无

rwe的赋值:无

当主类和分类都未实现load方法,主类会在运行时,首次消息发送时完成初始化。通过cls->data()获取ro的方法列表,包含了主类+分类中的所有方法。后续没有分类加载流程,也没有rwe的赋值

所以这种情况下,分类的方法也是从MachO中加载

6.5 多分类 + 主类

  • 全部分类均为懒加载分类,未实现load方法,进入懒加载分类流程
  • 主类为非懒加载类,即实现load方法。部分或全部分类为非懒加载分类,也实现load方法,进入非懒加载分类 + 非懒加载类流程
  • 主类为懒加载类,未实现load方法。仅一个分类实现load方法,进入非懒加载分类 + 懒加载类流程
  • 主类为懒加载类,未实现load方法。超过一个分类实现load方法,此时进入一个特殊流程

6.5.1 超过一个非懒加载分类 + 懒加载类

主类不会在_read_images流程中初始化,而是在load_images流程,进入方法加载方法流程
image.png

进入prepare_load_methods函数,通过_getObjc2NonlazyCategoryList,读取MachO__DATA__objc_nlcatlist节,获取非懒加载的分类列表。循环分类列表,通过分类找到所属主类,对其进行初始化
image.png

如果一个主类有很多分类,循环时会进入realizeClassWithoutSwift函数多次,但内部有判断条件,如果已经初始化,直接返回,所以不用担心主类会初始化多次

之后会进入熟悉的主类初始化流程:realizeClassWithoutSwiftmethodizeClassattachToClass

进入attachToClass函数,找到主类中的所有分类。调用attachCategories函数,进行分类的初始化
image.png

  • 没有任何属性、方法的空分类,不会包含到列表中

主类加载流程:load_imagesprepare_load_methodsrealizeClassWithoutSwiftmethodizeClassattachToClass

分类加载流程:主类加载流程→attachCategories

rwe的赋值:分类加载流程的attachCategories函数中,调用cls->data()->extAllocIfNeeded()

6.6 分类方法的排序

非懒加载分类 + 非懒加载类超过一个非懒加载分类 + 懒加载类两种情况,分类方法列表和主类方法列表分开存储,结构为:[分类A, 分类B, 分类C, 主类]

在分类或主类中,各自的方法列表已经进行地址升序。但不同的分类和主类之间,也会出现同名方法,此时如何保证查找到的方法,是排序在最前面分类中的方法呢?

在消息慢速查找流程中,调用search_method_list_inline函数进行二分查找的外部,还有一个针对分类和主类的大循环,如果二分查找在分类中找到该方法,直接停止循环并返回IMP。所以外部的循环,一定会保证找到的方法是排序在最前面分类中的方法
image.png

  • 循环方式,即:内存平移

而其他几种情况,分类方法和主类方法存储在一个列表中,它们按照函数地址进行升序排序。使用二分查找寻找到该方法后,必须保证它是同名方法中排序在最前面的,所以内部还会进行遍历和--操作,一直找到前面没有方法,或者前面的方法名称不同为止
image.png

7. attachList分析

当主类实现load方法,分类也实现load方法时,分类通过load_images进行加载

流程:load_imagesloadAllCategoriesload_categories_nolockload_categories_nolockattachCategoriesattachLists

7.1 load_categories_nolock

load_categories_nolock函数中,循环获取分类,有多少分类,就会循环多少次
image.png

由于主类实现load方法,在map_images流程中已完成类的加载,所以进入cls->isRealized()分支,调用attachCategories函数
image.png

7.2 attachCategories

attachCategories函数中,读取传入分类下的方法列表
image.png

将方法列表,存储到mlists的结尾
image.png

调用prepareMethodLists函数,对分类下的方法列表进行排序
image.png

调用rwe->methods.attachLists,将分类中的方法插入到rwe
image.png

传入的方法列表为method_list_t类型的二级指针

7.3 attachList

因为list里存储的了主类的方法列表,进入一对多流程
image.png

通过setArray开辟新列表的空间,array_t结构体指针类型

将主类的方法列表,method_list_t结构体指针存储到新列表的结尾

遍历分类的方法列表,通过addedLists[i],从二级指针中,获取method_list_t结构体指针,存储到新列表的指定索引位置,保证分类的方法排在前面

最终存储结构:[分类方法列表, 主类方法列表]

无论是分类还是主类的方法列表,结构统一为method_list_t结构体指针类型

对添加完分类方法列表后的rwe进行打印
image.png

方法列表method_list_t中存储的是指针地址,通过get方法进行内存平移,获取索引对应的指针地址,而指针地址指向method_t结构体

总结

分类的本质:

  • 分类本质为category_t结构体,编译阶段,设置的数据只是临时占位
  • 分类没有所属的元类,所以实例方法和类方法都存储在分类中
  • 分类没有生成属性的getter/setter方法,所以分类中需要使用关联对象

什么是rwrorwe?

  • ro属于clean memory,在编译即确定的内存空间,只读,加载后不会改变内容的空间
  • rw属于dirty memory,是运行时结构,可读可写,可以向类中添加属性、方法等,在运行时会改变的内存
  • rwe相当于类的额外信息,因为在实际使用过程中,只有很少的类会真正的改变他们的内容,所以为避免资源的消耗就有了rwe

类的结构里面为什么会有rwro以及rwe?

  • rw中包括rorwe

◦ 类的属性和方法在运行时可进行更改,例如:使用categoryRuntime API。因为ro是只读的,所以需要在rw中来跟踪这些东西
◦ 常开发中,只有少部分类会被更改它们的属性和方法,为了让dirty memory占用更少的空间,把rw中可变的部分抽取出来为rwe

  • 运行时,如果需要动态向类中添加方法协议等,会创建rwe,并将ro的数据优先attacherwe中。在读取时会优先返回rwe的数据,如果rwe没有被初始化,则返回ro的数据
  • dirty memoryclean memory更昂贵,当系统物理内存紧张的时候,会回收掉clean memory内存,所以clean memory越多越好,dirty memory越少越好

指针强转数据结构:

  • 地址指针的特性,可对其进行取值、内存平移和类型强转等操作
  • 指针类型的强转,只要内存结构一致,即可正常解析

rwe的赋值:

  • rwe在使用categoryRuntime API时,调用extAllocIfNeeded函数赋值
  • 如果rwe不存在,调用extAlloc函数进行开辟

attachCategories反推思路:

  • 流程一:realizeClassWithoutSwiftmethodizeClassattachToClassattachCategories
  • 流程二:_read_imagesload_categories_nolockattachCategories,触发方式未知
  • 流程三:load_imagesloadAllCategoriesload_categories_nolockattachCategories

attachList算法:

  • 如果list不存在,进入零对一流程
  • 如果list存在,但元素中不包含数组,进入一对多流程

◦ 旧列表存在,即为1,否则为0
◦ 新列表元素总数 = 旧列表元素总数 + 追加元素总数
◦ 新列表开辟空间,并进行setArray操作,标记列表为数组
◦ 将旧列表元素写入新列表的结尾
◦ 遍历追加元素,依次存储到新列表中

  • 如果list中的标记为数组,进入多对多流程

◦ 获取旧列表中的元素总数
◦ 新列表元素总数 = 旧列表元素总数 + 追加元素总数
◦ 新列表开辟空间
◦ 将旧列表中的元素,从结尾开始,依次从新列表的结尾处开始存储
◦ 遍历追加元素,依次存储到新列表中
◦ 将新列表进行setArray操作,标记列表为数组

  • 一个类可能会存在多个分类,此时的存储结构为[分类A, 分类B, 分类C,主类]
  • 分类A/B/C的顺序和编译顺序有关,谁排在前面不一定

非懒加载分类 + 非懒加载类

  • 主类加载流程:map_imagesmap_images_nolock_read_imagesrealizeClassWithoutSwiftmethodizeClassattachToClass
  • 分类加载流程:load_imagesloadAllCategoriesload_categories_nolockload_categories_nolockattachCategoriesattachLists
  • rwe的赋值:分类加载流程的attachCategories函数中,调用cls->data()->extAllocIfNeeded()

非懒加载分类 + 懒加载类

  • 主类加载流程:map_imagesmap_images_nolock_read_imagesrealizeClassWithoutSwiftmethodizeClassattachToClass
  • 分类加载流程:无
  • rwe的赋值:无

懒加载分类 + 非懒加载类

  • 主类加载流程:map_imagesmap_images_nolock_read_imagesrealizeClassWithoutSwiftmethodizeClassattachToClass
  • 分类加载流程:无
  • rwe的赋值:无

懒加载分类 + 懒加载类

  • 主类加载流程:lookUpImpOrForwardinitializeAndLeaveLockedinitializeAndMaybeRelockrealizeClassMaybeSwiftAndUnlockrealizeClassMaybeSwiftMaybeRelockrealizeClassWithoutSwiftmethodizeClassattachToClass
  • 分类加载流程:无
  • rwe的赋值:无

多分类 + 主类

  • 全部分类均为懒加载分类,未实现load方法,进入懒加载分类流程
  • 主类为非懒加载类,即实现load方法。部分或全部分类为非懒加载分类,也实现load方法,进入非懒加载分类 + 非懒加载类流程
  • 主类为懒加载类,未实现load方法。仅一个分类实现load方法,进入非懒加载分类 + 懒加载类流程
  • 主类为懒加载类,未实现load方法。超过一个分类实现load方法,此时进入一个特殊流程

超过一个非懒加载分类 + 懒加载类

  • 主类加载流程:load_imagesprepare_load_methodsrealizeClassWithoutSwiftmethodizeClassattachToClass
  • 分类加载流程:主类加载流程→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结构体