1. Category简介
category是Objective-C 2.0之后添加的语言特性,category的一般应用场景有下面几个,一般最常用的是前三个:
-
为已经存在的类添加方法
Category可以添加方法,可以通过关键字关联添加属性,不能添加成员变量。 -
把类的实现分开在几个不同的文件里面
好处是:1、可以减少单个文件的体积 2、可以把不同的功能组织到不同的category里 3、可以由多个开发者共同完成一个类 4、可以按需加载想要的category等 - 声明私有方法
- 模仿多继承
- 把framework的私有方法分开
2. Category和Extension的区别
extension并不是匿名的category,它们是完全不同的
-
category
是运行期决议的,extension
在编译器决议是类的一部分,在编译器和头文件的@interface
和实现文件里的@implement
一起形成了一个完整的类。伴随着类的产生而产生,也随着类的消失而消失。 -
extension
一般用来隐藏类的私有消息,你必须有一个类的源码才能添加一个类的extension
,所以对于系统一些类,如nsstring
,就无法添加类扩展,而Category
,你无需知道类的源码,可以重写类的方法 -
extension
可以添加实例变量,category
不能添加实例变量
3. objc源码中的category
3.1 先开看下category的struct
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的定义也可以看出category的可为(可以添加实例方法,类方法,甚至可以实现协议,添加属性)和不可为(无法添加实例变量)
至于原因,可以看上一篇,从类的结构分析为什么不能添加变量,可以添加属性和方法。
4. Category添加过程
4.1 怎样把category中的添加的方法添加到类中?
void attachCategories(Class cls, category_list *cats, bool flush_caches)
:把category中的添加的方法、属性、协议添加到类中,这里cats是category_t类型数组,一个类可能会添加多个category。(这里attachCategories函数只截取方法添加部分,以方法添加为例,属性和协议添加类似)
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
/// 申请空间malloc,这里的mlists数组中包含的对象是指针,指针指向category的methodlist
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
int mcount = 0;
int i = cats->count;
bool fromBundle = NO;
while (i--) {///get newest categories first,最新的放在第一个
auto& entry = cats->list[i];
/// 获取到category方法列表的指针
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);
///attachLists方法:把保存的所有category的方法添加到rw->methods中
rw->methods.attachLists(mlists, mcount);
/// 释放空间
free(mlists);
}
从函数中可以很清晰的看到,category中的方法添加到类的一个过程:先申请sizeof为cats->count的一个指针数组空间,把所有category的方法列表的指针存到这个数组(最新的在第一位),之后rw->methods.attachLists(mlists, mcount);
把这个包含方法列表指针的数组添加到类原来的方法列表里,从而实现了category中添加方法的功能。
4.2 Category重写方法,会覆盖原来类的方法吗?
如果你有在Category中重写过方法,应该会很清楚,调用该重写方法的时候会调用Category中的,应该也会考虑过:
- Category重写方法是覆盖了原来类的方法吗?
- 还能不能调用到原来的方法?
我们来看下把category中的方法添加到类的方法列表的具体实现应该能解答这两个疑惑:
来看下rw->methods.attachLists(mlists, mcount);
的实现过程(篇幅过长,只截取部分代码):
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;
/// realloc:对内存进行大小的调整
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
/// array()的count
array()->count = newCount;
/// memmove函数:将原来的lists拷贝到最后面
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
/// 把添加的list放到前面
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
...
}
我有在函数里面添加了部分注释,大家应该能看明白,简单来说就是:
1. Category中的方法列表会添加到类方法列表的前面,所以并没有覆盖原来的方法。
2. 至于为什么会调用category中的方法,是因为当方法调用时,会从类的方法列表中查找方法的实现,Category的方法在列表的前面,所以会被调用。
3. 类的方法没被覆盖,所以我们还是可以调用到类的原来的方法的,只要从方法列表中最后开始查找,找到的第一个就是类的方法了。
网友评论