Category是Objective-C 2.0之后增加的语言特性,Category的主要作用是为已经存在的类添加方法。Category是对装饰模式的典型实践(装饰模式(Decorator)是指在不修改原有类的前提下,动态地给这个类添加一些方法)。
Category的底层结构
category的加载处理过程
(1)通过Runtime加载某个类的所有Category数据;
(2)把所有Category的方法、属性、协议数据,合并到一个大数组中,后面参与编译的Category数据,会在数组的前面;
(3)将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面。
【注意】category并没有完全的替换掉原有类的同名方法,category的方法被放置在新方法列表的前面,而原来类的方法被放到后面,在runtime中,遍历方法列表查找时,找到了category的方法后,就会停止遍历,这就是我们平时所说的“覆盖”方法。
某个类如果存在两个category,而且这两个Category中存在方法名相同的方法,那么根据buildPhases->Compile Sources里面的从上至下的编译顺序,会调用后参与编译的那个分类的方法。
Category的局限性
- Category只能给某个现有的类添加方法,不能添加成员变量;
- Category中也可以添加属性,只不过@property只会生成setter和getter的声明,不会生成setter方法和getter方法的实现,也不会自动合成带下划线的成员变量;
- 如果category中的方法和类中原有方法同名,运行时会优先调用category中的方法。也就是,category中的方法会覆盖掉类中原有的方法。所以开发中尽量保证不要让分类中的方法和原有类中的方法名相同。避免出现这种情况的解决方案是给分类的方法名统一添加前缀。比如category_。
+load方法
+load方法的特点
- +load方法会在runtime加载类、分类时调用其对应的+load方法。
- 每个类、分类的+load方法,在程序运行过程中只调用一次。
+load方法的调用顺序
根据Apple的objc-runtime-new.mm的源码分析可知+load方法的调用顺序如下:
1.先调用类的+load方法
- 按照编译的先后顺序调用(先编译,先调用)
- 调用子类的+load方法之前会先调用父类的+load
2.再调用分类的+load方法
- 按照编译先后顺序调用(先编译,先调用)
+initialize方法
+initialize方法的特点
- +initialize方法会在类第一次接收到消息的时候调用
+initialize方法的调用顺序
调用顺序:先调用父类的+initialize,再调用子类的+initialize。也就是先初始化父类,再初始化子类,且每个类只会初始化1次。
+initialize和+load的最大区别是,+initialize是通过objc_msgSend进行调用的,而+load方法是通过函数指针直接调用+load方法。正因为+initialize是通过objc_msgSend进行调用的,所以+initialize有以下特点:
- 如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次);
- 如果分类实现了+initialize,就覆盖类本身的+initialize调用。
有关Category常见的面试题
1.Category的使用场合是什么?
- 可以为现有类添加新的方法;
- 模拟多继承
- 将一个类中的代码分散出来管理(将类的实现分布到几个不同文件中)
- 把framework的私有方法公开
2.Category的实现原理是什么?
Category编译之后的底层结构是struct_category_t,里面存储着分类的对象方法、类方法、属性、协议信息。在程序运行的时候,runtime会将Category的数据,合并到类信息中(类对象、元类对象中)
3.Category和Class Extension的区别是什么?
Class Extension是在编译期决定,Category由运行时决定。也就是说Class extension在编译的时候,它的数据就已经包含在类信息中;而Category(分类)是在运行时,才会将分类数据合并到类信息中(类对象、元类对象中)。
4.Category能否添加成员变量?如果可以,如何给Category添加成员变量?
默认情况下,由于分类底层结构的限制,不能直接给Category添加成员变量,但是可以间接实现。
通过“关联对象”的方式来给Category添加成员变量。“<objc/runtime.h>”中提供的关联对象的API有以下3个:
- (1)添加关联对象
void objc_setAssociatedObject(id object, const void * key,id value, objc_AssociationPolicy policy)
- (2)获得关联对象
id objc_getAssociatedObject(id object, const void * key)
- (3)移除所有的关联对象
void objc_removeAssociatedObjects(id object)
5.Category中有load方法吗?load方法是什么时候调用的?load方法能继承吗?
有+load方法。load方法在runtime(运行时)加载类、分类的时候调用。+load方法能继承。
【注意】+load方法一般都由系统自动调用,不手动调用。
6.load方法和initialize方法的区别是什么?它们在category中的调用顺序是怎样的?以及出现继承时它们之间的调用过程是怎样的?
区别:
(1)调用方式不同。load是根据函数地址直接调用,而initialize是通过objc_msgSend调用
(2)调用时刻不同。load是runtime加载类、分类的时候调用(只会调用1次),而initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)。
load、initialize的调用顺序:
- load
1> 先调用类的+load方法
a)按照编译的先后顺序调用(先编译,先调用)
b)调用子类的+load方法之前会先调用父类的+load
2> 再调用分类的+load方法
a)按照编译先后顺序调用(先编译,先调用)
- initialize
1> 先初始化父类;
2> 再初始化子类(可能最终调用的是父类的initialize方法)。
OC的类扩展(Class Extension)
类扩展(Class Extension),也称为匿名分类。可以为原来的类 扩展一些属性、成员变量以及方法。