iOS里面的Category
什么是category
Category
可以说是我们在开发中经常用到的一种手法了,尤其是在对一些我们看不到源码的类中添加方法,这种情况经常用到项目的工具类开发中,那么它究竟是怎么一个东西呢?这一篇就来探究一下Category
。
Category
是在Objective-C 2.0
之后添加的语言特性,它的主要作用是为已经存在的类里面添加方法,我们可以利用这个特性把一个类的实现放在不同的文件中,缩小单个文件体积,多人开发一个类,按需加载不同的category
等。
category与extension
很多人会混淆extension
和category
,extension
看起来很像一个匿名的category
,但是它们完全不是一个东西,我们先来看看什么是extension
。
extension
被开发者称之为扩展、延展、匿名分类。extension
看起来很像一个匿名的category
,但是extension
和category
几乎完全是两个东西。和category
不同的是extension
不但可以声明方法,还可以声明属性、成员变量。extension
一般用于声明私有方法,私有属性,私有成员变量。
下面就是一个extension
,extension
通常会写在.m
文件中。
extension
在编译期决议,它就是类的一部分,在编译期和头文件里的@interface
以及实现文件里的@implement
一起形成一个完整的类,它伴随类的产生而产生,亦随之一起消亡。extension
一般用来隐藏类的私有信息,你必须有一个类的源码才能为一个类添加extension
,所以你无法为系统的类比如NSString
添加extension
。
@interface ViewController ()
@end
category的结构体
在我之前学习block
的时候会去到结构体层面去看它的实现原理,一切都很清晰明了,同样对于category
来说我们去看看它的结构体是怎样的。
typedef 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;
} category_t;
这个看起来很熟悉,我们对比一下类的结构体是什么样的:
//Class也表示一个结构体指针的类型
typedef struct objc_class *Class;
struct objc_class {
Class _Nonnull isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class _Nullable super_class OBJC2_UNAVAILABLE;
const char * _Nonnull name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list * _Nullable ivars OBJC2_UNAVAILABLE;
struct objc_method_list * _Nullable * _Nullable methodLists OBJC2_UNAVAILABLE;
struct objc_cache * _Nonnull cache OBJC2_UNAVAILABLE;
struct objc_protocol_list * _Nullable protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
从category
的定义也可以看出category
的可以添加实例方法,类方法,甚至可以实现协议,添加属性和但是分类却没有objc_ivar_list
,因此无法添加实例变量。
分类可以添加属性?
分类当然可以添加属性,只不过我们一般都说分类不能直接添加属性
,我们在分类中增加属性,只是在属性的列表里添加了一个属性,并没有生成对应的ivar
,Xcode
也会警告没有实现getter
和setter
方法,所以我们无法通过getter
和setter
方法操作属性。
但是我们可以使用关联对象在分类中动态的为一个类增加属性。
//这里用@selector(color)来用作 const void *key 的指针
- (UIColor *)color {
return objc_getAssociatedObject(self, _cmd);
}
- (void)setColor:(UIColor *)color {
objc_setAssociatedObject(self, @selector(color), color, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
Category的真面目
我们使用clang
的命令去看看category
到底会变成什么:
//实例方法列表
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[1];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
//分类中定义的方法
{{(struct objc_selector *)"printName", "v16@0:8", (void *)_I_MyClass_MyAddition_printName}}
};
//属性列表
static struct /*_prop_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count_of_properties;
struct _prop_t prop_list[1];
} _OBJC_$_PROP_LIST_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
//分类中添加的属性
{{"name","T@\"NSString\",C,N"}}
};
extern "C" __declspec(dllexport) struct _class_t OBJC_CLASS_$_MyClass;
static struct _category_t _OBJC_$_CATEGORY_MyClass_$_MyAddition __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
"MyClass",
0, // &OBJC_CLASS_$_MyClass,
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MyClass_$_MyAddition,
0,
0,
(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_MyClass_$_MyAddition,
};
static void OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition(void ) {
_OBJC_$_CATEGORY_MyClass_$_MyAddition.cls = &OBJC_CLASS_$_MyClass;
}
#pragma section(".objc_inithooks$B", long, read, write)
__declspec(allocate(".objc_inithooks$B")) static void *OBJC_CATEGORY_SETUP[] = {
(void *)&OBJC_CATEGORY_SETUP_$_MyClass_$_MyAddition,
};
static struct _class_t *L_OBJC_LABEL_CLASS_$ [1] __attribute__((used, section ("__DATA, __objc_classlist,regular,no_dead_strip")))= {
&OBJC_CLASS_$_MyClass,
};
static struct _class_t *_OBJC_LABEL_NONLAZY_CLASS_$[] = {
&OBJC_CLASS_$_MyClass,
};
static struct _category_t *L_OBJC_LABEL_CATEGORY_$ [1] __attribute__((used, section ("__DATA, __objc_catlist,regular,no_dead_strip")))= {
&_OBJC_$_CATEGORY_MyClass_$_MyAddition,
};
我们的分类代码会被编译成以上的样子,在我们的项目运行的时候,运行时库的原理是会将分类中定义的实例方法、协议以及属性添加到类上,把分类的类方法和协议添加到类的metaclass
上。
category与+load
在类和category
中都可以有+load
方法,我们在类的+load
方法调用的时候,我们可以调用category
中声明的方法么?
答:可以调用,因为附加
category
到类的工作会先于+load
方法的执行。
这么些个+load
方法,调用顺序是咋样的呢?
答:
+load
的执行顺序是先类,后category,而category
的+load
执行顺序是根据编译顺序决定的。
参考文章:https://tech.meituan.com/2015/03/03/diveintocategory.html