美文网首页
系统底层源码分析(7)——Category(分类)的编译和运行

系统底层源码分析(7)——Category(分类)的编译和运行

作者: 无悔zero | 来源:发表于2021-05-26 19:23 被阅读0次

1、什么是 Category(分类)?
分类是 Objective-C 2.0 添加的语言特性,主要作用是为已经存在的类添加方法。
虽然继承也能为已有类增加新的方法,但有时继承关系会增加复杂度,在运行时,也无法与父类的原始方法进行区分。

通常 Category(分类)有以下几种使用场景:

  • 把类的不同实现方法分开到不同的文件里。
  • 声明私有方法。
  • 模拟多继承。
  • framework 私有方法公开化。

2、Category(分类)和Extension(扩展)的区别是什么?

  • Extension是在编译阶段与该类同时编译的,它的数据就已经包含在类信息中;
    Category是在运行时,才会将数据合并到类信息中。

  • Extension可以直接添加属性;
    但是Category不可以,只能通过runtime添加。

  • Extension中声明的方法只能在该类的 @implementation 中实现,所以无法对系统的类(例如 NSString 类)使用 Extension
    Category可以为系统的类添加方法。

3、为什么 Category(分类)不能像 Extension(扩展)一样添加成员变量?
因为 Extension是在编译阶段与该类同时编译的,所以作为类的一部分,可以在编译阶段为类添加成员变量;
Category是在运行时期间决定的,成员变量的内存布局已经在编译阶段确定好了(也就是说objc_class结构体大小是固定的),如果在运行时阶段添加成员变量的话,就会破坏原有类的内存布局,所以 Category无法直接添加成员变量,只能通过runtime添加setter/getter达到添加属性的效果(属性和成员变量不一样)。
另外,分类的methodList是一个二维数组,虽没办法扩展methodLists指向的内存区域,但可改变methodList指向的内存区域的值(存储的是指针),所以可以动态添加方法。

从上面我们知道,扩展和分类是有区别的,那么现在就从编译和运行两个阶段了解一下分类。

1. 编译时期

我们把文件编译成c++源码看一下:

.m文件由 OCC++ 源码方法如下:
打开终端,执行 cd 文件所在目录 命令,
然后执行 clang -rewrite-objc xxx.m
之后xxx.m 所在目录下就会生成一个 xxx.cpp 文件,这就是相关的 C++ 源码。

编译分类文件,会创建一个_Category_t结构体(如果一个类有多个分类,则 Category 数组中对应多个 Category ),分类的本质就是结构体,存储着分类的实例方法、类方法、属性、协议:

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;//属性列表
};
//Person 类的 Category 结构体赋值
static struct _category_t _OBJC_$_CATEGORY_Person_$_Eat __attribute__ ((used, section ("__DATA,__objc_const"))) = 
{
    "Person", // 类名
    0, // &OBJC_CLASS_$_Person, // cls
    (const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_Person_$_Eat, // 实例方法列表
    0, // 类方法列表
    0, // 协议列表
    0, // 属性列表
};

运行时,再把各个分类的结构体添加合并到原本类中。所有类的分类都会放到__objc_catlist列表中:

static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
    &_OBJC_$_CATEGORY_Person_$_Eat,
};
2. 运行时期

Category 是如何被加载的?

  • dyld是苹果的动态加载器,用来加载 image(注意这里 image 不是指图片,而是 Mach-O 格式的二进制可执行文件)。
  • 当程序启动时,系统内核首先会加载 dyld , 然后 dyld 会将我们 APP 所依赖的各种库加载到内存空间中,其中就包括 libobjc 库(OCruntime), 这些工作,是在 APPmain 函数执行前完成的。
  • 接着通过Runtime加载某个类的所有Category数据。先把原类数据(方法、属性、协议)的数组指针后移(后移位置取决于分类大小),再把各个分类的数据(方法、属性、协议)合并放到前面,所以当对象调用分类和类中都有的同名方法时,先调用分类的方法(后编译先调用)。

dyld加载的流程大致是这样:

  1. 配置环境变量;
  2. 加载共享缓存;
  3. 初始化主 APP;
  4. 插入动态缓存库;
  5. 链接主程序;
  6. 链接插入的动态库;
  7. 初始化主程序:OC, C++ 全局变量初始化;
  8. 返回主程序入口函数。

{\large\text{作者:行走少年郎 链接:https://www.jianshu.com/p/b08bbe3613ab 来源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。}}

下一篇:Category(分类)加载流程
下下篇:Category(分类)关联属性

相关文章

网友评论

      本文标题:系统底层源码分析(7)——Category(分类)的编译和运行

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