如果原来的类和分类中有相同的方法,那么最终执行的是分类方法。
编译完,每个分类都会生成一个category_t结构体,里面存储名称、对象方法列表、类方法列表、协议方法列表、属性列表。
1 | struct category_t { |
在合并分类的时候,其方法列表等不会覆盖原来类中的方法,是共存的。但分类的方法在前面,原来类的方法在后面,调用的时候,就会调用分类中的方法,如果多个分类的相同方法,后编译的分类会被调用。
如果想要执行被“覆盖”的类定义方法,可以逆序遍历方法列表,第一次取得的就是类定义的方法:
1 | - (void)foo{ |
类对象/元类对象才是最终存储分类实例/类方法、属性、协议的地方。
扩展问题
Category的原本使用场景
区分不同的功能模块,使用分类单独实现。
Category的实现原理
分类编译后是category_t结构体,里面存储着分类的对象方法、类方法、属性、协议信息,在程序运行时,Runtime会把分类的数据合并到类信息(类对象、元类对象)中。
Category与Extension的区别
Extension在编译的时候,其数据已经包含在类信息中。Category在运行时才会把数据合并到类信息中。
Category为什么不能添加成员变量
category_t结构体只能存储属性,但没有存储objc_ivar_list结构体,没有用存储成员变量的地方,所以不能添加成员变量。
+load
方法的执行
Runtime在加载类和分类的时候,会调用所有的+load
方法,即使没有该类还没使用。
调用方式:函数地址直接调用。
调用时机:加载类和分类时调用一次,只会调用一次。
+load
方法调用顺序:
- 调用类的
+load
- 按照编译顺序进行;
- 先调父类,再调子类;
- 按照编译顺序调用分类的
+load
方法。
先去调用类的+load
方法,若有父类则先调用父类的+load
方法,再去调用分类的+load
方法。
+initialize
方法的执行
+initialize
需要在使用(调用方法)类的时候才会调用。其调用顺序跟普通方法一致,即若有分类实现的+initialize
方法,则调用分类的方法。
调用方式:objc_msgSend
调用。
调用时机:在类第一次接收到消息时调用,所以父类可能会执行多次(只有父类实现了+initialize
方法,而子类没有实现)。