Category(分类)简介
Category(分类)是Objective-C 2.0 添加的语言特性,主要作用是为已经存在的类添加分类。Category可以做到既不子类化,也不侵入一个类的源码的情况下,为原有的类添加新的方法,从而实现扩展一个类或者分离一个类的目的。在日常开发中我们常常使用Category为已有的类扩展功能。
虽然继承也能为已有类增加新的方法,而且还能直接增加属性,但继承关系增加了不必要的代码复杂度,在运行时,也无法与父类的原始方法进行区分。所以我们可以优先考虑使用自定义Category(分类)。
通常Category(分类)有以下几种使用场景:
把类的不同实现方法分开到不同的文件里。
声明私有方法
模拟多继承
将framework私有方法公开化
Category(分类)和Extension(扩展)
Category (分类)看起来和Extension(扩展)有点相似。Extension (扩展)有时候也称为匿名分类。但两者实质上是不同的东西。Extension(扩展)是在编译阶段与该类同时编译的,是类的一部分,而且Extension(扩展)中声明的方法只能在该类同时编译的@implementation 中实现,这也就意味着,你无法对系统的类(例如 NSString 类)使用Extension(扩展)。而且和Category(分类)不同的是,Extension(扩展)不但可以声明方法,还可以声明成员变量,这是Category(分类)所做不到的。
(为什么Category(分类)不能像 Extension(扩展)一样添加成员变量?
因为Extension(扩展)是在编译阶段与该类同时编译的,就是类的一部分。既然作为类的一部分,且与类同时编译,那么就可以在编译阶段为类添加成员变量。而Category(分类)不同,Category(分类)的特性是:可以在运行时阶段动态地为已有类添加新行为。Category(分类)是在运行时期间决定的。而成员变量的内存布局已经在编译阶段确定好了,如果在运行阶段添加成员变量的话,就会破坏原有类的内存布局,从而造成可怕的后果,所以Category(分类)无法添加成员变量。 )
Category的实质
Category结构体简介
我们之前知道了Object (对象)和 Class(类)的实质分别是objc_object结构体和objc_class结构体,这里Category也不例外,在objc-runtime-new.h 中,Category(分类)被定义为 category_t 结构体。category_t 结构体的数据结构如下:
typedef struct category_t *Category;
struct category_t {
const char *name; // 类名
classref_t cls; // 类,在运行时阶段通过 clasee_name(类名)对应到类对象
struct method_list_t *instanceMethods; // Category 中所有添加的对象方法列表
struct method_list_t *classMethods; // Category 中所有添加的类方法列表
struct protocol_list_t *protocols; // Category 中实现的所有协议列表
struct property_list_t *instanceProperties; // Category 中添加的所有属性
};
从Category(分类)的结构体定义中也可以看出,Category(分类)可以为类添加对象方法、类方法、协议、属性。同时,也能发现Category(分类)无法添加成员变量。
Category的C++源码
想要了解Category的本质,我们需要借助于Category的C++源码。首先呢,我们需要写一个继承自NSObject的Person类,还需要写一个Person+Additon的分类。在分类中添加对象方法,类方法,属性,以及代理。
/***********************Person+Addition.h 文件****************************/
#import "Person.h"
// PersonProtocol 代理
@protocol PersonProtocol <NSObject>
- (void)PersonProtocolMethod;
+ (void)PersonProtocolClassMethod;
@end
@interface Person (Addition) <PersonProtocol>
/* name 属性*/
@property (nonatomic, copy) NSString *personName;
// 类方法
+ (void)printClassName;
// 对象方法
- (void)printName;
@end
/***********************Person+Addition.m 文件****************************/
#import "Person+Addition.h"
@implementaion Person (Addition)
+ (void)printClassName {
NSLog(@"printClassName");
}
- (void)printName {
NSLog(@"printName");
}
#pragma mark - <PersonProtocol> 方法
- (void)PersonProtocolMethod {
NSLog(@"PersonProtocolMethod");
}
+ (void)PersonProtocolClassMethod {
NSLog(@"PersonProtocolClassMethod");
}
@end
Category 由 OC 转 C++ 源码方法如下:
1.在项目中添加Person 类文件 Persion.h 和 Person.m, Person 类继承自NSObject.
2.在项目中添加Person类的Category文件Person+Addition.h和Person+Addition.m,并在Category中添加相关对象方法,类方法,属性,以及代理。
3.打开『终端』,执行 cd XXX/XXX 命令,其中 XXX/XXX 为 Category 文件 所在的目录。
4.继续在终端执行 clang -rewrite-objc Person+Addition.m
5.执行完命令之后,Person+Addition.m 所在目录下就会生成一个 Person+Addition.cpp 文件,这就是我们需要的 Category(分类) 相关的 C++ 源码。
得到Person+Addition.cpp文件之后,就会神奇的发现:这是一个3.7M大小,拥有近10W行代码的庞大文件。
Category的相关C++源码在文件的底部,删除其他无关代码,会剩下差不多200行代码。下边我们根据Category结构体 的不同结构,分模块来讲解一下。
Category结构体
// Person 类的 Category 结构体
struct _category_t {
const char *name;
struct _class_t *cls;
const struct _method_list_t *instance_methods;
const struct _method_list_t *class_methods;
const struct _protocol_list_t *protocols;
const struct _prop_list_t *properties;
};
// Person 类的 Category 结构体赋值
static struct _category_t _