一: category
category
是Objective-C 2.0
之后添加的语言特性,别人口中的 分类、类别其实都是指的category
。category
的主要作用是为已经存在的类添加方法。除此之外,apple
还推荐了category
的另外两个使用场景。
1.1: 什么是Category
可以把类的实现分开在几个不同的文件里面。这样做有几个显而易见的好处。
- 可以减少单个文件的体积
- 可以把不同的功能组织到不同的category里
- 可以由多个开发者共同完成一个类
- 可以按需加载想要的category
- 声明私有方法
apple
的SDK中就大面积的使用了category
这一特性。比如UIKit中的UIView。apple把不同的功能API进行了分类,这些分类包括UIViewGeometry、UIViewHierarchy、UIViewRendering
等。
不过除了apple推荐的使用场景,广大开发者脑洞大开,还衍生出了category的其他几个使用场景:
- 模拟多继承(另外可以模拟多继承的还有protocol)
- 把framework的私有方法公开
1.2: category特点
-
category
只能给某个已有的类扩充方法,不能扩充成员变量。 -
category
中也可以添加属性,只不过@property
只会生成setter
和getter
的声明,不会生成setter
和getter
的实现以及成员变量。 - 如果
category
中的方法和类中原有方法同名,运行时会优先调用category
中的方法。也就是,category
中的方法会覆盖掉类中原有的方法。所以开发中尽量保证不要让分类中的方法和原有类中的方法名相同。避免出现这种情况的解决方案是给分类的方法名统一添加前缀。比如category_
。 - 如果多个
category
中存在同名的方法,运行时到底调用哪个方法由编译器决定,最后一个参与编译的方法会被调用。
如下图,给UIView添加了两个category
(one
和 two
),并且给这两个分类都添加了名为log的方法:
在viewController中引入这两个category的.h文件。调用log方法:
当编译顺序如下图所示时,调用UIView + one.m
的log方法,如下图:
将
UIView + one.m
移动到UIView + two.m
上面,调用UIView + two.m
的log方法,如下图:
1.3: 调用优先级
分类(category) > 本类 > 父类。即,优先调用cateory
中的方法,然后调用本类方法,最后调用父类方法。
注意:category
是在运行时加载的,不是在编译时。
1.4: 为什么category不能添加成员变量?
Objective-C
类是由Class类型来表示的,它实际上是一个指向objc_class
结构体的指针。它的定义如下:
typedef struct objc_class *Class;
objc_class
结构体的定义如下:
struct objc_class {
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE; // 父类
const char *name OBJC2_UNAVAILABLE; // 类名
long version OBJC2_UNAVAILABLE; // 类的版本信息,默认为0
long info OBJC2_UNAVAILABLE; // 类信息,供运行期使用的一些位标识
long instance_size OBJC2_UNAVAILABLE; // 该类的实例变量大小
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE; // 该类的成员变量链表
struct objc_method_list **methodLists OBJC2_UNAVAILABLE; // 方法定义的链表
struct objc_cache *cache OBJC2_UNAVAILABLE; // 方法缓存
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE; // 协议链表
#endif
} OBJC2_UNAVAILABLE;
在上面的objc_class
结构体中,ivars
是objc_ivar_list
(成员变量列表)指针;methodLists
是指向objc_method_list
指针的指针。在Runtime
中,objc_class
结构体大小是固定的,不可能往这个结构体中添加数据,只能修改。所以ivars
指向的是一个固定区域,只能修改成员变量值,不能增加成员变量个数。methodList
是一个二维数组,所以可以修改*methodLists
的值来增加成员方法,虽没办法扩展methodLists
指向的内存区域,却可以改变这个内存区域的值(存储的是指针)。因此,可以动态添加方法,不能添加成员变量。