分类与扩展
# 分类(Category):
分类中的可以写@property, 但不会生成setter/getter方法, 也不会生成实现以及私有的成员变量(编译时会报警告);
可以在分类中访问原有类中.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; // 分类的类属性列表
};
2
3
4
5
6
7
8
9
10
# 关于类的 + (void)load 与 + (void)initialize 的区别
相同点
- 两个函数都是系统自动调用,因此无需手动调用(如果手动调用则与普通函数调用类似);
- 两个函数都会隐式调用各自父类对应的 + (void)load 或 + (void)initialize 方法,即子类调用方法之前,会优先调用其父类对应的方法;
- 两个函数内部都使用了锁,因此两个函数都是线程安全的;
不同点
调用时机不同:+ (void)load 在 main 函数之前执行,即 objc_init Runtime初始化时调用,且只会调用一次。+ (void)initialize 在类的方法首次被调用时执行,每个类只会调用一次,但父类可能会调用多次;
调用方式不同:+ (void)load 是根据函数地址直接调用,+ (void)initialize 是通过消息发送机制即 objc_msgSend(id self, SEL _cmd, ...) 调用;
子类父类调用关系不同:
- 如果子类没有实现 + (void)load,则不会调用其父类的 + (void)load 方法。
- 如果子类没有实现 + (void)initialize,则会调用其父类的方法,因此父类的 + (void)initialize 可能会调用多次;
类别 category 对调用的影响不同:
如果 category 中实现了 + (void)load,则会优先调用原类的的 + (void)load,再调用 category 的,即优先级为:父类 > 原类 > category
没有继承关系的不同类中的 + (void)load 的调用顺序跟 Compile Sources 顺序有关,即在前面的优先编译的类或者 category 先调用( 备注: 所有类的 + (void)load 优先级大于 category 的优先级);
同一个类的 category 的 + (void)load 的调用顺序跟 Compile Sources 顺序有关,即在前面的优先编译的 category 会先调用;
同一镜像中主工程的 + (void)load 方法优先调用,然后再调用静态库的 + (void)load 方法。有多个静态库时,静态库之间的执行顺序与编译顺序有关,即它们在 Link Binary With Libraries 中的顺序;
根据runtime的消息传递机制中的核心函数void objc_msgSend(id self,SEL cmd,...)来发送消息,先从当前类中查找调用的方法,若没有找到则继续从其父类中一层层往上找,那么对于category重写同一个方法,则在消息传递的过程中,会最先找到category中的方法并执行该方法。对于多个分类调用同一个方法,Xcode在运行时是根据buildPhases->Compile Sources里面的从上至下顺序编译的,编译时通过压栈的方式将多个分类压栈,根据后进先出的原则,后编译的会被先调用,(插入顶部添加,即[methodLists insertObject:category_method atIndex:0]; 所以objc_msgSend遍历方法列表查找SEL 对应的IMP时,会先找到分类重写的那个,调用执行)当objc_msgSend找到方法并调用之后,就不再继续传递消息,所以形成所谓的覆盖。
- 不同镜像中,动态库的 + (void)load 方法优先调用,然后再调用主工程的 + (void)load,多个动态库的 + (void)load 方法的调用顺序跟编译顺序有关,即它们在 Link Binary With Libraries 中的顺序;
如果 category 中实现了 + (void)initialize,则原类的 + (void)initialize 将不会再调用
多个 category 中同时实现了 + (void)initialize 方法时,Compile Sources中顺序最下面的一个,即最后一个被编译 Category 的 + (void)initialize 会执行;
# 类别与类扩展的区别:
类别中原则上只能增加方法(能添加属性的的原因只是通过runtime解决无setter/getter的问题而已); 类扩展不仅可以增加方法,还可以增加实例变量(或者属性),只是该实例变量默认是@private类型的( 用范围只能在自身类,而不是子类或其他地方);
类扩展中声明的方法没被实现,编译器会报警,但是类别中的方法没被实现编译器是不会有任何警告的。这是因为类扩展是在编译阶段被添加到类中,而类别是在运行时添加到类中。
类扩展不能像类别那样拥有独立的实现部分(@implementation部分),也就是说,类扩展所声明的方法必须依托对应类的实现部分来实现。
定义在 .m 文件中的类扩展方法为私有的,定义在 .h 文件(头文件)中的类扩展方法为公有的。类扩展是在 .m 文件中声明私有方法的非常好的方式。