zoukankan      html  css  js  c++  java
  • 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
    ",__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
    ",__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 排序。 

    验证

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

    以上。

  • 相关阅读:
    Jquery easyui datagrid 删除多行问题
    Winform 程序部署生成
    20120915
    Winform 程序部署生成
    Jquery EasyUi datagridwindowform数据问题
    eclipse快捷键大全(转)
    c/c++ 图像RGB位存储,位操作
    常用的几款在线代码转换工具
    WPF基础之路由事件一
    WPF中的依赖属性与附加属性
  • 原文地址:https://www.cnblogs.com/zhangzhang-y/p/13832290.html
Copyright © 2011-2022 走看看