Tino又想吃肉了

category

Word count: 936Reading time: 3 min
2022/01/02

简单梳理category的底层实现

Category

在iOS中,我们都知道category(分类)可以用来扩展类的协议、方法甚至是属性,当然我们需要知道在category中扩展的属性其实是相当于无用的,因为category是在运行时决议,在编译期过后类对象的内存布局已经确定,无法再新增成员变量ivar(属性=setter+getter+ivar)。那category的底层结构是什么样的呢?

category_t

我们可以在objc源码中,找到category的底层结构。需要注意的是category_t结构体才是目前的Objective-C中的category的实现,旧版category已经弃用。

struct category_t中,可以看到有分类的name、对应的class等值,同时还有instanceMethodsclassMethodsprotocolsinstanceProperties_classProperties等list,说明category支持为class添加实例方法、类方法、协议、实例对象、类对象。

当然,由于category是在程序启动后,runtime加载image之后才注册到类中,此时的类对象布局早已确定,所以category添加的属性如果不自行使用关联对象实现和添加setter、getter方法的话,是相对于没有用的。

-w770

注册

在程序启动后,dyld绑定了runtime,当dyld将二进制image载入后会通知runtime的回调进行image的mapping,这时runtime才会将category注册到对应的类中。

在objc源码中,我们可以搜索到这个函数,函数体内先申请runtimeLock,之后就遍历header调用load_categories_nolock()函数来加载category。
-w667

接着看看load_categories_nolock()函数内做了什么。

-w797

-w755

在以上代码中,我们可以看到category载入后调用了attachCategories()方法来将methods等内容注册到对应的类中。

进入到attachCategories这个函数中,我们可以看到runtime是怎样将category中添加的函数添加到Class中的。

-w794

这里的注释告诉我们,categories的加载顺序是按load order来排序的,而添加的时候oldest categories优先,也就是说先编译的category会先被注册到类中。

由于几个数据的添加流程大同小异,下面的代码只看method的。

-w732

在这段代码前,runtime定义了ATTACH_BUFSIZ为64,理由是Apple认为很少会有类的categories超过64个之多。可以看到,将method注册到类中的关键函数就是attachLists()了。

进到attachLists()中,可以发现在这里对class_rw_ext_t的methods做了一些操作,其中,调用了两个C函数,memmovememcpy。在这之前,在setArray()中还对array进行了扩容。

memmove中,将lists中原有的元素拷贝到了array()->lists + addedCount地址,实际上就是将list内的元素后移了,将前面的空间腾出来准备给新的元素写入。

在这里就可以看出,由于先编译的category会先被加载,所以先加载的category中的method在method list中是在后加载的category的后面,而在调用时从头开始扫描,所以在多个categories同时添加了同一个方法的实现,后编译的category的方法会被调用。

接下来的memcpy函数将addedLists,也就是category中的method拷贝到array()->lists地址,即数组的头部。

-w759

自此之后,category中添加的内容就被注册到Class中了。

总结

从结构上,category为开发者提供了在runtime时向Class中添加协议、方法、属性的能力,但由于它是在运行时注册的,所以添加的属性无法直接使用,需要使用关联对象和自行实现setter、getter方法。同时,多个categories在注册时的顺序跟编译顺序息息相关,在最终后添加的方法在method list中会在先添加的方法前面,所以后编译的category中的同名方法有更高的优先级,会被调用。

CATALOG
  1. 1. Category
    1. 1.1. category_t
    2. 1.2. 注册
    3. 1.3. 总结