美文网首页
分类的加载

分类的加载

作者: 深圳_你要的昵称 | 来源:发表于2020-10-21 00:29 被阅读0次

前言

上回分析了类的加载的大致流程,我们知道了懒加载类和非懒加载类的系统调用栈信息,通过断点调试追踪,我们知道了系统在调用realizeClassWithoutSwift去实现类的信息,通过methodizeClass方法,在进行方法列表绑定时,又通过prepareMethodLists对方法列表中的方法进行排序的,然后将类里的方法列表,属性列表、协议列表以及分类信息做一个绑定的过程。


然后分类的信息绑定,如下图:

这个attachToClass就是分类绑定的过程。

attachToClass的大致流程

下面看看attachToClass的源码:

    void attachToClass(Class cls, Class previously, int flags)
    {
        runtimeLock.assertLocked();
        ASSERT((flags & ATTACH_CLASS) ||
               (flags & ATTACH_METACLASS) ||
               (flags & ATTACH_CLASS_AND_METACLASS));

        auto &map = get();
        auto it = map.find(previously);

        if (it != map.end()) {
            category_list &list = it->second;
            if (flags & ATTACH_CLASS_AND_METACLASS) {
                int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;
                attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);
                attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);
            } else {
                attachCategories(cls, list.array(), list.count(), flags);
            }
            map.erase(it);
        }
    }

大致理解:

  • auto &map = get(); 先获取一个map,类似集合。
    Type &get() {
        return *reinterpret_cast<Type *>(_storage);
    }
  • auto it = map.find(previously); 寻找previously类是否在其中。
  iterator find(const_arg_type_t<KeyT> Val) {
    BucketT *TheBucket;
    if (LookupBucketFor(Val, TheBucket))
      return makeIterator(TheBucket, getBucketsEnd(), true);
    return end();
  }

find函数可知,map中的元素是BucketT这种类型,我们暂且理解为桶结构模板。如果找到了previously类,则返回其对应的桶结构的索引下标iterator,否则返回end()集合的末尾。
using iterator = DenseMapIterator<KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT>; iterator是DenseMapIterator结构体类型👇

  • if (it != map.end()) 不等于map.end就表示找到了。
  • category_list &list = it->second; iterator的second成员变量的类型是category_list👇
  • 接着调用attachCategories,这是核心代码,后面再仔细分析其流程。
  • map.erase(it);完成绑定后移除

至此,我们知道了分类绑定的调用链顺序:

realizeClassWithoutSwift --> methodizeClass --> attachToClass --> attachCategories

绑定的大致过程分析完了,我们知道了how-->是如何加载的,那when-->何时加载到内存的呢?
既然类的加载是在_read_images中完成的,那么分类是否也在其中呢?

回到_read_image


根据注释和log日志,我们在3629到3633行找到了关于分类相关的代码-->load_categories_nolock

load_categories_nolock

  • 实例方法、协议和实例属性绑定在类cls中,而类方法、协议和类属性绑定在类cls的ISA即元类中。
  • 都是通过attachCategories中去绑定的。

以上只是我们根据类的加载,去分析分类何时加载到内存的,并没有去证明这个过程,接下来我们用示例证明分类的加载的时机,是否在_read_image中?

示例断点追踪

  1. 创建分类
#import "LGPerson.h"

NS_ASSUME_NONNULL_BEGIN

@interface LGPerson (LG)

@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;

- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
- (void)cate_instanceMethod3;
+ (void)cate_classMethod1;

@end

NS_ASSUME_NONNULL_END

#import "LGPerson+LG.h"

@implementation LGPerson (LG)

- (void)cate_instanceMethod1 {
    NSLog(@"%s", __func__);
}

- (void)cate_instanceMethod2 {
    NSLog(@"%s", __func__);
}

- (void)cate_instanceMethod3 {
    NSLog(@"%s", __func__);
}

+ (void)cate_classMethod1 {
    NSLog(@"%s", __func__);
}

@end
  1. 打断点


  2. run运行



断点根本没有进来,看来不是在_read_images里去触发分类的加载时机,那时机到底在哪里?我们直接在load_categories_nolock里打个断点,再看看调用栈:


调用栈是:

load_images-->loadAllCategories-->load_categories_nolock-->attachCategories

小结

以上我们分析了分类加载how 与 when,请看下图👇


attachCategories流程分析

接下来我们具体看看attachCategories是如何将分类的信息绑定到类里面的。

  1. 注入代码,强制断点跳到我们要研究的类LGPerson中:

  2. LGPerson 和分类LG, 添加+load方法-->非懒加载化,可以简化加载的流程


  1. run运行
    断点进入

cat中确实是LG分类,cat的信息是从header_info中而来,而header_info是从machO中读取的。

查看cat中的分类方法:


接着看加载:


attachCategories如法炮制设置断点:

其中mlists是二维数组

插入分类LG的方法列表

分类LG没有声明属性

也没有声明协议


只有一个分类

prepareMethodLists-->fixupMethodList

attachList 共3部分条件判断

  • 第一种情况是第二方框,当addedLists来了之后,将addedLists的第一个元素赋值给list,也就是说,当list不存在时,就会从0-1;
  • 第二种情况是第三个方框的代码,当有list之后,加入很多个list,总的意思就是将新加入的放在前面,旧的数据放在后面。它这样的实现是一种算法思维,LRU算法,也就是说当分类和主类中的方法存在一样时,就加载主类的方法。
  • 第三种情况是加载多个分类方法,它的功能是先扩容,然后将后加入的分类放在前面。
    断点进入的是第二种情况:



    查看oldList的方法列表,其实就是LGPerson本类的方法列表



    copy分类方法到内存

    因为放在了索引0处,所以分类方法在本类的方法的前面。

    下面是lldb验证分类的方法在array()前面。

(lldb) p array()
(list_array_tt<method_t, method_list_t>::array_t *) $0 = 0x00000001006121a0
(lldb) p $0.lists[0]
(method_list_t *) $2 = 0x0000000100008048
  Fix-it applied, fixed expression was: 
    $0->lists[0]
(lldb) p $2.get(0)
(method_t) $3 = {
  name = "cate_instanceMethod1"
  types = 0x0000000100003e10 "v16@0:8"
  imp = 0x0000000100003a40 (KCObjc`-[LGPerson(LG) cate_instanceMethod1])
}
  Fix-it applied, fixed expression was: 
    $2->get(0)
(lldb) p $2->get(1)
(method_t) $4 = {
  name = "cate_instanceMethod2"
  types = 0x0000000100003e10 "v16@0:8"
  imp = 0x0000000100003a70 (KCObjc`-[LGPerson(LG) cate_instanceMethod2])
}
(lldb) p $2->get(2)
(method_t) $5 = {
  name = "cate_instanceMethod3"
  types = 0x0000000100003e10 "v16@0:8"
  imp = 0x0000000100003aa0 (KCObjc`-[LGPerson(LG) cate_instanceMethod3])
}
(lldb) p $0.lists[0].get(1)
(method_t) $6 = {
  name = "cate_instanceMethod2"
  types = 0x0000000100003e10 "v16@0:8"
  imp = 0x0000000100003a70 (KCObjc`-[LGPerson(LG) cate_instanceMethod2])
}
  Fix-it applied, fixed expression was: 
    $0->lists[0]->get(1)
(lldb) p $0.lists[1]
(method_list_t *) $7 = 0x00000001000081d8
  Fix-it applied, fixed expression was: 
    $0->lists[1]
(lldb) p $7.get(0)
(method_t) $8 = {
  name = "kc_instanceMethod1"
  types = 0x0000000100003e10 "v16@0:8"
  imp = 0x0000000100003b40 (KCObjc`-[LGPerson kc_instanceMethod1])
}
  Fix-it applied, fixed expression was: 
    $7->get(0)
(lldb) p $7->get(1)
(method_t) $9 = {
  name = "kc_instanceMethod3"
  types = 0x0000000100003e10 "v16@0:8"
  imp = 0x0000000100003b10 (KCObjc`-[LGPerson kc_instanceMethod3])
}
(lldb) p $7->count
(uint32_t) $10 = 8
(lldb) 

至此,分类的方法就与本类的方法加载到了一起。

以上通过设置判断条件,断点追踪,得到以下结论:

本类和分类均是非懒加载类时,分类方法的加载在load_images中,本类的方法在_read_images中,然后再把分类的方法贴到本类方法列表中,其中分类的方法靠前。

其它情况

根据排列组合,还有以下三种情况:

  1. 主类非懒加载 分类懒加载
  2. 主类懒加载 分类非懒加载
  3. 主类 分类均懒加载

我们稍微改变下示例,声明两个分类LGALGB

@interface LGPerson (LGA)
- (void)cateA_1;
- (void)cateA_2;
- (void)cateA_3;
@end

@interface LGPerson (LGB)
- (void)cateB_1;
- (void)cateB_2;
- (void)cateB_3;
@end
1.主类非懒加载 分类懒加载

realizeClassWithoutSwift中观察类cls里的data()数据,发现是class_ro_t结构体类型👇


查看baseMethodLists里面的方法

上图可知,在realizeClassWithoutSwift中,cls的两个分类的方法已经绑定在方法列表中了,再看左侧调用栈信息,可知是在_read_images时,cls的分类就已经加载完毕了。

2.主类懒加载 分类非懒加载

注意:main方法中没有调用LGPerson的任何方法:


直接运行,看看日志记录👇

从日志中查看,没有触发load_categories_nolock方法,最终走的是attachToClass,那么我们在attachToClass中断点看看,只有主类的方法,如下图👇


但是我并没有在main方法中调用关于LGPerson的任何方法,LGPerson本是懒加载类,按道理只有消息发送时才能触发LGPerson类的加载,但是现在确已经加载了。
接着我们的断点继续走,来到attachCategories中:

接着断点走到attachLists

接着来到else第三种情况,上面分析过:先扩容,然后将后加入的分类放在方法列表的前面。

综上,主类懒加载,分类非懒加载时,

  1. 主类会以非懒加载的方式提前加载数据,
  2. 分类方法添加的调用链是:
    realizeClassWithoutSwift --> methodizeClass --> attachToClass --> attachCategories --> attachLists
3.主类 分类均懒加载

依旧main方法中没有调用LGPerson的任何方法,此时类信息和分类信息都不应该加载
运行观察日志:果然没有加载👇


再触发方法,让它们加载

既然是消息发送,那我们去到lookUpImpOrForward中断点查看,如何触发的懒加载

断点锁定了LGPerson,继续走,如下图👇

因为懒加载,类LGPerson肯定未实现,那么会触发realizeClassMaybeSwiftAndLeaveLocked


接着走,来到realizeClassWithoutSwift

后面流程就不用走大家都应该知道。至此,我们锁定了一个新的方法realizeClassMaybeSwiftMaybeRelock,同理,我们在此方法中增设条件

放开所有断点,run运行项目,查看日志:

上图证明了,不论是懒加载的类,还是懒加载的分类,都是在消息发送触发的前提条件下,才会去触发类信息与分类信息的加载。

总结

分类数据的加载时机如下:

  • 非懒加载类 + 非懒加载分类:其数据的加载在load_images方法中,首先对类进行加载,然后把分类的信息贴到类中。
  • 非懒加载类 + 懒加载分类:其数据加载在read_image就加载数据,数据来自MachO文件的data,是在编译时期就已经完成,data中除了类的数据,还有分类的数据,与类绑定在一起。
  • 懒加载类 + 懒加载分类:其数据加载推迟到第一次消息发送时,数据同样来自MachO文件的data,data在编译时期就已经完成。
  • 懒加载类 + 非懒加载分类:只要分类实现了load,会迫使主类提前加载,即在_read_images中不会对类做实现操作,需要在load_images方法中触发类的数据加载,即rwe初始化,同时加载分类数据。

相关文章

  • iOS底层原理19:类和分类的加载

    前面已经探究了类的加载流程,类分为懒加载类和非懒加载类,他们有不同加载流程,下面来探究下分类的加载,以及分类和类搭...

  • 分类的加载

    前言 上回分析了类的加载[https://www.jianshu.com/p/4c69eae1146e]的大致流程...

  • 06.Objective-C 分类(Categroy)的底层结构

    问题 Category 的底层结构 分类的加载处理过程 分类中load方法加载过程 initialize方法的调用...

  • 类的加载(下)

    上篇文章我们了解了类的加载和分类的数据准备 这篇我们继续分析分类的加载时机以及是如何加载到类中的 当类加载时,进入...

  • iOS开发中 +load 和 +initialize特点

    + load方法 在程序启动时,会加载所有的类和分类,并调用所有类和分类的 + load方法 先加载父类,再加载子...

  • Various classifier comparisons o

    加载数据 分类器 评估

  • iOS之学习总结

    一、load 方法 1、load方法加载 +Load方法会在runtime加载类、分类时调用 每个类、分类的+lo...

  • 反射

    类加载器:加载过程 加载,连接,初始化 分类: Bootstrap ClassLoader 根类加载器:核心类的加...

  • OC底层原理探索—类的加载(3)

    上一篇我们探索了类的加载流程等一系列方法以及懒加载类和非懒加载类这节课我们来探索下分类的加载流程 分类的本质 首先...

  • iOS class_ro_t和class_rw_t的区别 cat

    本文主要介绍class_ro_t和class_rw_t的区别、分类加载过程以及多个分类加载的问题 class_ro...

网友评论

      本文标题:分类的加载

      本文链接:https://www.haomeiwen.com/subject/prhrmktx.html