OC 底层探索 15、类的加载3 - 分类的加载

本文继续衔接 OC 底层探索 14、类的加载2 探索分类的加载。调试源码

一、分类的本质

分类的结构查看方法

1).cpp 文件

在 main.m 中任意添加一个分类信息的 .h .m 文件信息,编译生成cpp文件 clang -rewrite-objc main.m -o main.cpp

如下tu:

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 };

有 2 个 _method_list_t 分类的方法是要 attach 到本类cls上由本类进行调用的,分类是不存在元类的说法的。

2)objc 源码搜索 category_t

 

2、分类的方法 list

分类添加的属性系统不会给其生成 set/get 方法,我们可通过runtime 的 associate 进行set/get 方法的动态关联。

二、分类的加载

1、源码分析

继续OC 底层探索 14、类的加载2 对 methodizeClass() 源码分析,首先给 MyPerson 添加分类,并添加如下方法:

 

1、运行工程,方法 list 中只有本类的 2 个方法,并没有分类中的方法,见下图:

继续执行代码到 源码2的 47 行起,category 分类的 attach,此时 list 仍是有 2 个方法:

2、objc::unattachedCategories.attachToClass() 代码如下,源码1:

 1     void attachToClass(Class cls, Class previously, int flags)
 2     {
 3         runtimeLock.assertLocked();
 4         ASSERT((flags & ATTACH_CLASS) ||
 5                (flags & ATTACH_METACLASS) ||
 6                (flags & ATTACH_CLASS_AND_METACLASS));
 7 
 8         
 9         // 我加的调试代码
10         const char *mangledName = cls->mangledName();
11         const char *myPersonName = "MyPerson";
12         if (strcmp(mangledName, myPersonName) == 0) {
13             printf("%s 函数 func: %s\n",__func__,mangledName);
14         }
15         
16         auto &map = get();
17         auto it = map.find(previously);
18 
19         if (it != map.end()) {
20             category_list &list = it->second;
21             if (flags & ATTACH_CLASS_AND_METACLASS) {
22                 int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
23                 attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
24                 attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
25             } else {
26                 attachCategories(cls, list.array(), list.count(), flags);
27             }
28             map.erase(it);
29         }
30     }

但是运行并未走进 attachCategories 源码1的20行代码,我们跳进 attachCategories() 方法并对其进行断点(下面源码2的第41行):

3、attachCategories() 源码2:

 1 // Attach method lists and properties and protocols from categories to a class.
 2 // Assumes the categories in cats are all loaded and sorted by load order, 
 3 // oldest categories first.
 4 static void
 5 attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
 6                  int flags)
 7 {
 8     if (slowpath(PrintReplacedMethods)) {
 9         printReplacements(cls, cats_list, cats_count);
10     }
11     if (slowpath(PrintConnecting)) {
12         _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
13                      cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
14                      cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
15     }
16 
17     /*
18      * Only a few classes have more than 64 categories during launch.
19      * This uses a little stack, and avoids malloc.
20      *
21      * Categories must be added in the proper order, which is back
22      * to front. To do that with the chunking, we iterate cats_list
23      * from front to back, build up the local buffers backwards,
24      * and call attachLists on the chunks. attachLists prepends the
25      * lists, so the final result is in the expected order.
26      */
27     constexpr uint32_t ATTACH_BUFSIZ = 64;
28     method_list_t   *mlists[ATTACH_BUFSIZ];
29     property_list_t *proplists[ATTACH_BUFSIZ];
30     protocol_list_t *protolists[ATTACH_BUFSIZ];
31 
32     uint32_t mcount = 0;
33     uint32_t propcount = 0;
34     uint32_t protocount = 0;
35     bool fromBundle = NO;
36     bool isMeta = (flags & ATTACH_METACLASS);
37     auto rwe = cls->data()->extAllocIfNeeded();// rwe 初始化 --> 要对copy出的干净内存进行方法插入操作
38 
39     
40     // 我加的调试代码
41     const char *mangledName = cls->mangledName();
42     const char *myPersonName = "MyPerson";
43     if (strcmp(mangledName, myPersonName) == 0) {
44         printf("%s 函数 func: %s\n",__func__,mangledName);
45     }
46     
47     // 遍历 分类数据的准备(method property protocol)
48     for (uint32_t i = 0; i < cats_count; i++) {
49         auto& entry = cats_list[i];
50 
51         method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
52         if (mlist) {
53             if (mcount == ATTACH_BUFSIZ) {
54                 prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
55                 rwe->methods.attachLists(mlists, mcount);
56                 mcount = 0;
57             }
58             mlists[ATTACH_BUFSIZ - ++mcount] = mlist;// mlists 添加数据 mlist --> 从后向前 倒序插入 64-1 64-2... (二维数组)
59             fromBundle |= entry.hi->isBundle();
60         }
61 
62         property_list_t *proplist =
63             entry.cat->propertiesForMeta(isMeta, entry.hi);
64         if (proplist) {
65             if (propcount == ATTACH_BUFSIZ) {
66                 rwe->properties.attachLists(proplists, propcount);
67                 propcount = 0;
68             }
69             proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
70         }
71 
72         protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
73         if (protolist) {
74             if (protocount == ATTACH_BUFSIZ) {
75                 rwe->protocols.attachLists(protolists, protocount);
76                 protocount = 0;
77             }
78             protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
79         }
80     }
81     // 分类方法的 排序、附着关联
82     if (mcount > 0) {
83         prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);// 排序
84         rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);// attach
85         if (flags & ATTACH_EXISTING) flushCaches(cls);
86     }
87 
88     rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
89 
90     rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
91 }

tiprwe 的初始化 开辟:extAllocIfNeeded(),在以下几种场景(我们可操作的):

  1. static IMP addMethod()

  2. static SEL * addMethods()
  3. BOOL class_addProtocol()

  4. static bool _class_addProperty()

  5. 分类

4、继续源码分析

attachLists() 源码3:

 1 void attachLists(List* const * addedLists, uint32_t addedCount) {
 2         if (addedCount == 0) return;
 3 
 4         if (hasArray()) {
 5             // many lists -> many lists 多+多
 6             uint32_t oldCount = array()->count;
 7             uint32_t newCount = oldCount + addedCount;
 8             setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));// 扩容
 9             array()->count = newCount;
10             memmove(array()->lists + addedCount, array()->lists, 
11                     oldCount * sizeof(array()->lists[0]));// 移动旧的去后面
12             memcpy(array()->lists, addedLists, 
13                    addedCount * sizeof(array()->lists[0]));// cpy 新的在前面
14         }
15         else if (!list  &&  addedCount == 1) {
16             // 0 lists -> 1 list
17             // 0到1 - list 没有数据第一次 - 第0个元素给list,此时 list 是一维的
18             list = addedLists[0];
19         } 
20         else {
21             // 1 list -> many lists
22             // 1+many - 举例 many lists 是3个
23             List* oldList = list;
24             uint32_t oldCount = oldList ? 1 : 0;
25             uint32_t newCount = oldCount + addedCount;// 容量计算,旧+新的和 1+3=4
26             setArray((array_t *)malloc(array_t::byteSize(newCount)));// 开辟总大小的空间 newCount
27             array()->count = newCount;// array 的数量:是新的添加 manylists 后的数量
28             if (oldList) array()->lists[addedCount] = oldList;// 旧的list 放最后面 第3个位置
29             // memcpy(位置, 放谁, 大小)
30             // 把新的 lists 从起始位置0开始放
31             memcpy(array()->lists, addedLists, 
32                    addedCount * sizeof(array()->lists[0]));
33         }
34     }

分类的 attachLists 过程:

这里其实也可验证,为何我们天津爱的分类方法会优于本类方法调用 --> 本类方法并非被覆盖而是在后面了。 

2、源码验证

运行工程验证,如何走进 attachCategories() 呢?

给分类也添加 +load 方法,运行可以走了进去:

1、何时调用 attachCategories() 呢?

1.1. 断点 - 看堆栈信息

可以看到 load_categories_nolock() 之后走进了attachCategories(),搜索load_categories_nolock:

_read_images() 和 loadAllCategories() 两处 调了 load_categories_nolock(hi); 断点,重新运行工程,断点只走了 loadAllCategories()

--> loadAllCategories() 是在 load_images() 时调用,map_images 已经走完了。

 

1.2. 反推

已知必然会调 attachCategories(),全局搜索 attachCategories,找到下面两处进行了调用:

  1. attachToClass()
  2. load_categories_nolock()

通过断点调试发现走到了 load_categories_nolock().

attachCategories 调用流程图

问题:我们创建多个分类,只在 本类和其中一个分类中添加load方法 其他分类不加,那么不加load方法的分类是懒加载还是非懒加载处理呢?--> 下面的 ‘3’ 进行分析。

2、attachList 源码执行

执行到上面源码2的84行(同时见下图源码):

ATTACH_BUFSIZ = 64  mcount = 1 --> 内存平移 --> 平移到最后位置。

分类的 attachLists(),走进了条件 1+多 : 

3、分类加载的几种情况

1)本类、分类均实现 load

上面的流程即使如此,全部走了 load_images() 加载到数据,文章上面的过程已可验证。

2)本类实现 load 方法非懒加载,分类都不实现 load

运行见下图,methodizeClass() 方法执行,2个分类的方法数据都在 data() 里面了。(rwe 为 NULL 未开辟脏内存,data 在 macho 中)

上图 lldb 出的信息,排序:对3者相同的方法名的排序 - 后加的分类 方法在前,本类在最后。

3)本类和分类都懒加载 不实现 load 方法

第一次方法调用时加载到数据:

执行过程: _read_images() --> readClass(). readClass 中 baseMethodListcount 也是13个,数据也是从 data() 里面拿到的:

方法排序和 ‘1)’ 相同。

4)主类不实现 load 分类实现 load

通过堆栈信息查看加载流程:

methodList 一直到 执行到 attachToClass() 仍是只有9个方法:

attachToClass() --> 走进了 attachCategories() --> attachLists() .

可得结论:分类实现了load方法会迫使主类进行提前加载数据(但这里本类仍并非是非懒加载,只是被迫使提前加载了而已)。 

5)主类和分类 cateMore 非懒加载 - cateTwo 懒加载不实现load方法:

1、运行工程,MyPerson第一次进入 attachCategories() -->

cateTwo 的方法加载在 data 中(ro->baseMethods())取出,rwe 为 null 未开辟脏内存.

2、继续执行 MyPerson 类第二次进入 load_categories_nolock() --> attachCategories() -->

attachLists()rew 非 NULL,分类 cateMore 的方法会 attachLists 进来:

所有分类都会走非懒加载!!!对于同一个本类,只要有一个分类是非懒加载,其他所有分类都会非懒加载的

总结:

  • 分类懒加载: 分类的数据信息是从 data 中读取的;
  • 有一 or 多个分类非懒加载:主类会提前加载(早于 main 函数),并 attach 到所有非懒加载分类的信息
  • 都为懒加载:在第一次方法调用时从 data() 中取出数据

 


问题回顾:通过 OC 底层探索 14 对方法排序源码的分析已知,方法排序:

1、首先根据方法名的地址进行排序 - name 的 address

2、若方法重名则根据 sel 排序

  1. sel 混乱则进行 fixedUp  2. sel 没有混乱则根据 imp 排序。 

验证

文章上面的流程也可得验证。

以上。

posted @ 2020-10-18 00:27  张张_z  阅读(266)  评论(0编辑  收藏  举报