OC中的Category&Extension区别及其原理

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类的加载流程。

你可能感兴趣的:(OC中的Category&Extension区别及其原理)