1. Category的使用场景
Category
也叫分类
或类别
,是OC提供的一种扩展类的方式。不管是自定义的类还是系统的类,我们都可以通过Category
给原有类扩展方法(实例方法和类方法都可以),而且扩展的方法和原有的方法的调用方式是一模一样的。比如我项目中经常需要统计一个字符串中字母的个数,但是系统没有提供这个方法,那我们就可以用Category
给NSString
类扩展一个方法,然后只需引入Category
的头文件就可以和调用系统方法一样来调用扩展的方法。
// 给NSString类添加一个Category,并扩展一个实例方法
@interface NSString (QJAdd)
- (NSInteger)letterCount;
@end
// 在需要使用这个扩展方法的地方引入头文件 #import "NSString+QJAdd.h",然后就可以调用这个扩展方法了
- (void)test{
NSString *testStr = @"sdfjshdfjk.,d.889";
NSInteger letterCount = [testStr letterCount];
}
Category
除了用来给类进行扩展外,还有一种比较高级的用法,就是用来拆分模块
,将一个大的模块拆分成多个小的模块,方便进行维护和管理。什么意思呢?我就举一个很多开发人员都会存在的问题,就是AppDelegate
这个类。这个类是刚创建项目时自动生成的,用来管理程序生命周期的。在刚创建项目时,这个类中是没有多少代码的,但是随着项目的进行,越来越多的代码会被放在这个类里面。比如说集成极光推送、友盟、百度地图、微信SDK等各种第三方框架时,这些第三方框架的初始化工作,甚至是相关的业务逻辑代码都会放在这个类里面,这就导致随着APP的功能越来越复杂,AppDelegate
中的代码就会越来越多,有的甚至有几千行,看着就让人头皮发麻。
这时我们就可以利用Category
来对AppDelegate
进行拆分,首先我们就需要对AppDelegate
中的代码进行划分,把同一种功能的代码抽取出来放在一个分类里面。比如说我可以新建一个极光推送的分类,然后把所有和极光推送有关的代码都抽出来放入这个分类,把所有和微信相关的代码抽出来放进微信的分类中,后面又有新的功要添加的话我只需要新建分类就好了。维护的时候要改什么功能的代码就直接找相应的分类就好了。
// 把所有和极光推送有关的代码都抽出来放入这个分类
#import "AppDelegate.h"
@interface AppDelegate (JPush)
@end
2. Category的底层实现
在讲解这个问题之前,我们需要对OC方法调用的底层机制以及类对象的内存存储结构有一定了解,对于这一块不熟悉的可以先去看看我的另外一篇博客OC对象的本质。
我们先来思考这样一个问题,当我们通过Category
给一个类扩展了一个实例方法,我们调用这个实例方法时,它也是通过实例的isa指针找到类对象,然后在类对象的方法列表中去查找这个方法。那么Category
扩展的方法是如何被添加到类对象的方法列表中去的呢?是编译的时候添加进去的还是运行的时候添加进去的呢?
首先我们来看下Category
的内存存储结构,Category
底层其实就是一个category_t
类型的结构体,我们可以在objc4
源码的objc-runtime-new.h
文件中看到它的定义:
// 定义在objc-runtime-new.h文件中
struct category_t {
const char *name; // 比如给Student添加分类,name就是Student的类名
classref_t cls;
struct method_list_t *instanceMethods; // 分类的实例方法列表
struct method_list_t *classMethods; // 分类的类方法列表
struct protocol_list_t *protocols; // 分类的协议列表
struct property_list_t *instanceProperties; // 分类的实例属性列表
struct property_list_t *_classProperties; // 分类的类属性列表
};
从这个结构体可以看出,Category
中存储的不仅有方法列表,还有协议列表和属性列表。
我们每创建一个分类,在编译时都会生成这样一个结构体并将分类的方法列表等信息存入这个结构体。在编译阶段分类的相关信息和本类的相关信息是分开的。等到运行阶段,会通过runtime加载某个类的所有Category数据,把所有Category的方法、属性、协议数据分别合并到一个数组中,然后再将分类合并后的数据插入到本类的数据的前面。
想要了解详细的流程,可以去看源码</