Category&Extension的主要区别
Category和Extension都能对分类进行扩展,但是它们各自实现的作用却有所区别。
Category
Category又称为类别、类目、分类等,它的主要特点有:
- 可以用来给类添加新的方法
- 不能给类添加成员变量,但是可以通过runtime给分类添加关联对象
- 分类中使用@property定义变量,只会生成getter、setter方法的声明,而不会生成方法的实现和带下划线的成员变量
Extension
Extension又称类扩展、延展,它跟Category比的主要特点有:
- 可以给类添加成员属性,但是是私有变量
- 可以给类添加方法,也是私有方法
它们为什么会有以上区别呢?接下来我们通过clang编译它们的源码,查看底层C++的实现逻辑,从底层来分析这个原因。
Category&Extension的底层原理
首先创建一个自定义类TestObject,分别在本类、Category和Extension声明名一个个属性name、ext_name和cate_name,分别声明和实现一个方法testMethod、ext_testMethod和cate_testMethod,demo如下:
demo.jpeg
属性property底层原理对比分析
我们先到main.cpp文件截取到属性property相关的代码分析。
- 本类属性name和Extension属性ext_name相关代码
在截取的过程中,发现本类和Extension的属性的代码是被编译在一起的,一次我们放一起分析,查看他们编译时的情况:
extern "C" unsigned long OBJC_IVAR_$_TestObject$_name;
extern "C" unsigned long OBJC_IVAR_$_TestObject$_ext_name;
struct TestObject_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_name;
NSString *_ext_name;
};
static struct /*_ivar_list_t*/ {
unsigned int entsize; // sizeof(struct _prop_t)
unsigned int count;
struct _ivar_t ivar_list[2];
} _OBJC_$_INSTANCE_VARIABLES_TestObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_ivar_t),
2,
{{(unsigned long int *)&OBJC_IVAR_$_TestObject$_name, "_name", "@\"NSString\"", 3, 8},
{(unsigned long int *)&OBJC_IVAR_$_TestObject$_ext_name, "_ext_name", "@\"NSString\"", 3, 8}}
};
- Category属性相关代码
这里截取了Category的属性cate_name相关的代码:
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_TestObject_$_Cate __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_prop_t),
1,
{{"cate_name","T@\"NSString\",&,N"}}
};
分析:在这里我们发现Extension和本类的属性name、ext_name编译时的底层逻辑都是一样的,都能生成成员变量,而且Extension的成员变量跟本类的成员变量都是一同被编译到对象结构里面。而Category这里只有一个_prop_list_t结构体,没有生成相应的成员变量。
接下来我们在对比方法列表,看看属性的setter方法和getter方法情况。
接下来比较方法列表相关的代码。
- 本类和Extension相关代码
这里因为本类和Extension的方法列表是被编译在一起的,所以放在一起分析:
// @implementation TestObject
static void _I_TestObject_testMethod(TestObject * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_91163dcd57j_zw_xyry904bc0000gn_T_main_e5ae60_mi_0);
}
static void _I_TestObject_ext_testMethod(TestObject * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_91163dcd57j_zw_xyry904bc0000gn_T_main_e5ae60_mi_1);
}
static NSString * _I_TestObject_name(TestObject * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_TestObject$_name)); }
static void _I_TestObject_setName_(TestObject * self, SEL _cmd, NSString *name) { (*(NSString **)((char *)self + OBJC_IVAR_$_TestObject$_name)) = name; }
static NSString * _I_TestObject_ext_name(TestObject * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_TestObject$_ext_name)); }
static void _I_TestObject_setExt_name_(TestObject * self, SEL _cmd, NSString *ext_name) { (*(NSString **)((char *)self + OBJC_IVAR_$_TestObject$_ext_name)) = ext_name; }
// @end
static struct /*_method_list_t*/ {
unsigned int entsize; // sizeof(struct _objc_method)
unsigned int method_count;
struct _objc_method method_list[10];
} _OBJC_$_INSTANCE_METHODS_TestObject __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
10,
{{(struct objc_selector *)"testMethod", "v16@0:8", (void *)_I_TestObject_testMethod},
{(struct objc_selector *)"ext_testMethod", "v16@0:8", (void *)_I_TestObject_ext_testMethod},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_TestObject_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_TestObject_setName_},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_TestObject_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_TestObject_setExt_name_},
{(struct objc_selector *)"name", "@16@0:8", (void *)_I_TestObject_name},
{(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_TestObject_setName_},
{(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_TestObject_ext_name},
{(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_TestObject_setExt_name_}}
};
- Category方法列表相关代码
这里截取了Category的方法cate_testMethod相关的代码:
// @interface TestObject (Cate)
// @property(nonatomic, strong) NSString *cate_name;
// - (void)cate_testMethod;
/* @end */
// @implementation TestObject (Cate)
static void _I_TestObject_Cate_cate_testMethod(TestObject * self, SEL _cmd) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_kz_91163dcd57j_zw_xyry904bc0000gn_T_main_e5ae60_mi_2);
}
// @end
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_TestObject_$_Cate __attribute__ ((used, section ("__DATA,__objc_const"))) = {
sizeof(_objc_method),
1,
{{(struct objc_selector *)"cate_testMethod", "v16@0:8", (void *)_I_TestObject_Cate_cate_testMethod}}
};
分析:首先我们看到类的方法列表里面不近包含本类的,也包含Extension的,但是确不包含Category的方法,而Category的方法在编译时是单独存放的。而且本类和Extension都能生成属性的setter和getter方法,而Category却没有。
Category&Extension使用场景
经过上面的分析可以发现Extension它的底层实现跟@interface并没有多大差别,同样能声明方法和属性,同样能生成成员变量,但是Extension并没有自己的实现,它的实现跟本类是共用的。所以他一般用作方法和属性的声明,特别是经常用作私有化声明的时候会用到;
而Category则不一样,Category虽然生成不了成员变量,但是可以通过关联对象和属性配合使用,达到跟类的属性一样的使用效果。而且Category有自己的实现,这一个特性让Category可以对类进行模块化,比如说当一个类的代码比较多、比较复杂时,这时候为了便于代码的阅读和维护,通常可以利用Category分成不同的模块,各模块分别定义自己的接口和实现,这样既不会让这个类的代码看起来臃肿,同时让外部在访问这个类相关接口时没有感觉到Category和类的区别。
总结
其实Extension在原理上它是类的一部分,在编译时期会被编译到类结构里面,属于在编译时期确定的结构。而Category则是针对运行时而设计的,它是在运行时才会被加载到类结构里面。Category的加载又分为懒加载和非懒加载,懒加载是在类第一次被访问的时候跟类一起被加载,非懒加载是在程序启动过程中就跟类一起被加载了。想了解更多的关于Category的加载可以参考OC类的加载流程。
网友评论