问题
分类和扩展有什么区别?可以分别来做什么?分类有哪些局限性?分类的结构体里面有哪些成员?
分类的概念
Category 产生于 Objective-C 2.0,它的主要作用是为已经存在的类添加方法。它的使用场景:
比如说,我需要在当前控制器下处理一个字符串,写好处理方法,直接调用;但是,我别的类可能也需要调用该方法,我可以创建一个工具类,将该方法写在工具类中,也可以使用分类,为这个类添加额外的方法。
category 还有一些其他用法:
- 将类的实现分开写在几个分类里面(比如说为AppDelegate添加category)
- 可以减少耽搁文件的体积
- 可以把不同的功能组织到不同的category里
- 可以由多个开发者共同完成一个类
- 可以按需加载想要的category
所有的OC类和对象,在runtime层都是用struct表示的,Category 在runtime层用 category_t 表示(objc-runtime-new.h中可以找到其定义)。
struct category_t {
const char *name;
classref_t cls;
struct method_list_t *instanceMethods;
struct method_list_t *classMethods;
struct protocol_list_t *protocols;
struct property_list_t *instanceProperties;
};
- name: 类的名字
- cls: 类
- instanceMethods: category中所有给类添加的实例方法的列表
- classMethods: category中所有给类添加的类方法的列表
- protocols: category实现的所有协议的列表
- instanceProperties: category中添加的所有属性
从category的定义中可以看出,category可以添加类方法、实例方法、属性甚至可以实现协议。但是,不能添加实例变量。
category 和 extension 类比
extension 和 category 好像很像,但是完全不是一个东西。
extension 其实我们开发过程中经常再用,人们总是往往忽略身边的东西,我们经常会在.m 文件中写 @interface 这个东西,往往我们用来隐藏私有信息。它是在编译期决议的。它伴随着类的产生,以及类的消亡。你必须有一个类的源码,你才可以为一个类添加 extension。所以你无法为系统的类添加 extension。category 是在运行期决议的。extension可以添加实例变量,而category是不可以的(因为在运行期,对象的内部布局已经确定了。如果添加实例变量就会破坏类的内存布局)-- 为什么不能添加成员变量
分类使用注意
- 分类可以“覆盖”原来类中的同名方法,会导致原来类中的方法无法使用。(实际上并没有真正的覆盖,而是category的方法放在了新方法列表的最前面,而原来的方法放在方法列表的后面,这是因为运行时在查找方法的时候是顺着方法列表的顺序查找的,它只要一找到对应的方法,就会停止查找)-- 为什么我们建议在分类的方法前添加前缀
- 当分类、原类、原类的父类有相同的方法的时候,方法调用的优先级如下:分类 > 原类 > 父类
- category能不能添加属性?通过category的结构体可以看出,category的内存布局已经确定了,category可以添加属性,但是不会生成 成员变量以及setter/getter 方法。如果你在category文件中创建一个属性,当你在其他文件中调用时,会告诉你,找不到该方法的崩溃。当然,可以通过 runtime 动态生成 setter/getter 方法。但是无法生成成员变量。
具体为什么不能生成成员变量?
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 指向了一个固定区域,只能修改成员变量的值,不能增加成员变量的个数。方法列表是一个二维数组,可以修改 *methodLists的值来增加成员方法,虽然没办法扩展methodLists指向的内存区域,却可以改变这个内存区域的值(里面存的是指针),因此,可以动态添加方法,不可以添加成员变量。