Category

category是什么?

categoryObjective-C语言的一个特性,一般称之为“分类”,或者“类别”。其作用是在不修改类的源码的基础上,给类扩展一些接口。说的通俗一点,就是给已有类添加方法。

当需要为一个类添加新的方法时,在Objective-C中有四种方法可以做到。
1、直接在这个类里添加;但是这样,对于一些封装的类,会破坏其封装性,且对于一些系统库或者第三方动态库中的类,无法直接添加方法。
2、通过继承在子类中添加方法;继承的开销太大,且增加了代码的复杂度,同时也违背了继承的初衷,不建议使用。
3、通过协议,实现扩展一个类的方法;协议是个好东西,确实很实用,而且还能降低代码耦合度,但是实现起来过于复杂,代码量大,且对于只需要添加一两个方法时,就显得有点小题大做了。
4、使用category为类添加方法;一般用于比较简单的需求,例如仅仅只需要为类添加一两个特定功能的方法。

这里,我们重点讨论一下category的优缺点;
优点:
1)在不改变一个类的情况下,对一个已存在的类添加新的方法。
2)可以再没有源代码的情况下,对框架中的类进行扩展。
3)当一个类中的代码量太大时,可以按功能将代码中的方法放入到不同的category中,减小单个文件的体积。
缺点:
1)类别中的方法的优先级高于类中的方法,所以类别中的方法有可能会覆盖类中的方法。(因此在使用category时,需要注意命名,不要重复了)
2)不能直接添加成员变量。
3)可以添加属性,但是不会自动生成getter和setter方法。

category的实现原理

category是Objective-C的语言特性,探讨其实现原理,需要从runtime源码下手,下面就借助源码简单分析一下其实现的原理吧。
先找到category的定义:

1
2
#if __OBJC2__
typedef struct category_t *Category;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;

method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}

property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};

很明显,Category和Class一样,其本质都是一个结构体;这个结构体中定义了name、cls、实例方法、类方法、协议、属性等等,所以按理说,分类其实是可以为类添加方法、属性、协议的,但是不能添加实例变量,因为这个结构体中没有定义用来存放实力变量的指针变量。

category加载过程

category的加载过程,相对来说就比较复杂。需要从objc的初始化开始说。
来到故事最开始的地方_objc_init

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

前面几个函数的调用,其实都是准备工作,最主要的还是:
_dyld_objc_notify_register(&map_images, load_images, unmap_image);
当然,我们并不需要去研究dyld的加载过程,所以我们并不关注这个函数的具体实现,我们关心的,是这个函数的第一个参数map_images。他是一个回调函数指针,在* objc-runtime-new.mm中可以找到它:

1
2
3
4
5
6
7
void
map_images(unsigned count, const char * const paths[],
const struct mach_header * const mhdrs[])
{
mutex_locker_t lock(runtimeLock);
return map_images_nolock(count, paths, mhdrs);
}

这个函数比较简单,就两个函数的调用,第一个lock,猜测应该是和线程有关的,不做深入研究,主要看第二个函数map_images_nolock
这个函数有点长,但是其实我们并不需要对其深入研究,因为这个函数并不是加载category的细节,我们找到这个函数调用:

1
2
3
if (hCount > 0) {
_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
}

_read_images函数也很长,里面有对类、协议等的加载过程,当然category的加载也在,我们只需要找到关于category的那部分即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Process this category. 
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}

if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}

这一段的注释,其实也说的很明白了:首先,根据category的目标类注册category,然后,如果目标类已经实现的话,重新构建目标类的方法列表
这段代码看起来也并不复杂,其逻辑就是category的实例方法、协议或者属性,只要有一个不为空,即调用addUnattachedCategoryForClass函数来注册category,然后判断category的目标类是否实现,如果实现,就调用remethodizeClass重新构建目标类的方法列表rebuild the class's method lists
很显然,这里有两个关键函数:
注册函数:addUnattachedCategoryForClass;
重新构建方法的函数:remethodizeClass
先看看addUnattachedCategoryForClass函数的实现,探究注册过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static void addUnattachedCategoryForClass(category_t *cat, Class cls, 
header_info *catHeader)
{
runtimeLock.assertLocked();

// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;

list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}

这个函数可能看起来有点晕乎,但是仔细研究,其实也很简单:
首先,通过unattachedCategories ()取出runtime维护的MapTable —–category_map,这个MapTable是以TargetClass为key,category_list结构体对象为value的表,而category_list 结构体内,又定义了locstamped_category_t结构体数组,locstamped_category_t结构体中有定义了category_t结构体指针:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct locstamped_category_t {
category_t *cat;
struct header_info *hi;
};

struct locstamped_category_list_t {
uint32_t count;
#if __LP64__
uint32_t reserved;
#endif
locstamped_category_t list[0];
};

typedef locstamped_category_list_t category_list;

这就说明了category_list是通过locstamped_category_t 结构体数组来存储每一个category_t对象的;这也解释了为什么一个类可以定义多个category
而这段代码:

1
2
3
4
5
6
7
8
9
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
}

则说明了category的注册过程:
判断以TargetClass为key从MapTable中取出的类型为category_listlist指针是否为空,如果为空,则为list指针申请空间;如果不为空,则在list所占用的空间上,追加申请一个大小为sizeof(list->list[0])的空间,最后,将catcatHeader存储到list->list数组中。
以上就是category的注册过程,接下来看看方法的重新绑定过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list.
* Updates method caches for cls and its subclasses.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;

runtimeLock.assertLocked();

isMeta = cls->isMetaClass();

// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}

attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}

这个函数寥寥数语,其实也很好搞懂,通过目标类名,获取unattachedcategory_list,并调用attachCategories 函数,将category_list绑定到目标类上。
这个绑定过程,涉及到属性、协议以及方法的绑定,这里,我们重点分析category的方法重新绑定过程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
......
int mcount = 0;
bool fromBundle = NO;
int i = cats->count;
while (i--) {
auto& entry = cats->list[i];

method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
......
}
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
rw->methods.attachLists(mlists, mcount);
free(mlists);

这段代码其实就做了一件事:将cats->list数组中的数据取出,并存放到mlists数组中,
然后将mlists作为参数,传递以给attachLists函数,当然,同样作为参数的还有数组的count
所以咱们还得继续跟进到attachLists函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;

if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}

这里粗略的看看三个if else语句内的代码,其实不难看出,这三个地方,都是在做同一个操作—–将addedLists中的数据,拷贝到array()->lists。我们第一个if拿出来分析一下:

1
2
3
4
5
6
7
8
9
10
11
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}

首先,计算出新的array()->count:

1
2
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;

然后,用新的array()->count,为array()->lists分配内存:

1
2
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;

然后,将array()->lists中原来的数据,往后移addedCount*sizeof(array()->lists[0])个字节:

1
2
memmove(array()->lists + addedCount, array()->lists, 
oldCount * sizeof(array()->lists[0]));

最后,将addedLists中的数据复制到array()->lists的前addedCount*sizeof(array()->lists[0])个字节。

至此,整个category的分析过程就算完成了。