1、Category的基本使用
- 当业务比较复杂,需要将部分业务 分模块写出来,可以用分类
// Person.h文件
#import <Foundation/Foundation.h>
#import "Cat.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject
- (void)run;
@end
NS_ASSUME_NONNULL_END
--------------------------------------------------
// Person.m文件
#import "Person.h"
@implementation Person
- (void)setAge:(int)age {
NSLog(@"调用方法:%s %d",__func__,age);
}
- (void)run {
NSLog(@"run");
}
@end
// sleep分类
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person (sleep)
- (void)sleep;
@end
NS_ASSUME_NONNULL_END
#import "Person+sleep.h"
@implementation Person (sleep)
- (void)sleep {
NSLog(@"sleep");
}
@end
// eat分类
#import "Person.h"
NS_ASSUME_NONNULL_BEGIN
@interface Person (eat)
- (void)eat;
@end
NS_ASSUME_NONNULL_END
------------------------------------------------
#import "Person+eat.h"
@implementation Person (eat)
- (void)eat {
NSLog(@"eat");
}
@end
// 简单调用
- (void)testCategory {
self.person = [[Person alloc] init];
[self.person run];
[self.person sleep];
[self.person eat];
}
2、Category的本质
-
通过runtime加载某个类的所有Category数据
-
把所有Category的方法,属性,协议数据,合并到一个大数组中。
注意编译顺序:后参与编译的Category数据,会在数组的前面,后面会先调用。 -
将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面。反过来说,原来的类的数据会最后调用。
-
如果分类中含有和原来的类方法名相同的,就肯定会先调用分类的方法,而不会调用原来的类的方法。
3、+ load方法
-
+ load方法会在runtime加载类、分类时调用
-
每个类、分类的+load,在程序运行过程中只调用一次
-
调用顺序
\1. 先调用类的 +load
按照编译先后顺序调用(先编译,先调用)
调用子类的+load之前会先调用父类的+load
\2. 再调用分类的 +load
按照编译先后顺序调用(先编译,先调用) -
一般的方法[Object method]调用的本质是通过runtime的消息转发机制,进行调用的,所以会被分类覆盖
-
而+load方法是系统自动调用的,不是手动[Object method]调用的,通过函数指针,直接找到对应的内存地址进行调用
4、+initialize方法
- +initialize方法会在类第一次接收到消息时调用
- 调用顺序
- 先调用父类的+initialize,再调用子类的+initialize
[Person alloc]
// 第一次接受到消息的时候,就会调用initialize
// 伪代码
objc_msgSend([SuperClass class], @selector(alloc));
objc_msgSend([Person class], @selector(alloc));
// 所以说,先调用父类的+initialize,再调用子类的+initialize
- +initialize和+load很大的区别是,+initialize是通过objc_msgSend进行调用,所以有以下特点:
1.如果子类没有实现+initialize,会调用父类的+initialize(所以父类的+initialize可能会被调用多次)
// JHPerson 和 JHTeacher 都是 Person 的子类,并且只有Person这个父类实现了+initialize方法
- (void)testInit {
NSLog(@"--------");
[JHPerson alloc];
[JHPerson alloc];
[JHTeacher alloc];
[JHTeacher alloc];
}
// 打印结果:
2021-12-29 10:10:49.551502+0800 02-kvc[64187:3137661] --------
2021-12-29 10:10:49.551606+0800 02-kvc[64187:3137661] +[Person initialize]
2021-12-29 10:10:49.551677+0800 02-kvc[64187:3137661] +[Person initialize]
2021-12-29 10:10:49.551768+0800 02-kvc[64187:3137661] +[Person initialize]
- 分析,为什么会打印这样的结果呢?
// 伪代码
- (void)testInit {
NSLog(@"--------");
[JHPerson alloc];
[JHPerson alloc];
[JHTeacher alloc];
[JHTeacher alloc];
// 伪代码逻辑
if (JHPerson没有初始化) {
if (JHPerson的父类Person没有初始化) {
objc_msgSend([Person class], @selector(initialize)); // 第一次调用父类
}
objc_msgSend([JHPerson class],@selector(initialize)); // 调用子类,子类没实现,去找到父类调用,第二次调用父类
}
if (JHTeacher没有初始化) {
if (JHTeacher的父类Person没有初始化) { // 父类已经初始化了,不会进来
objc_msgSend([Person class], @selector(initialize));
}
objc_msgSend([JHTeacher class],@selector(initialize)); // 调用子类,子类没实现,去找到父类调用,第三次调用父类
}
}
- 根据以上分析,所以会最终调用3次initialize方法
2.如果分类实现了+initialize,就覆盖类本身的+initialize调用
(因为是消息机制,所以第2点很容易理解,也不是真正意义上的覆盖,而是先去调用了分类的+initialize,就不会再继续寻找类本身的+initialize了)
5、load、initialize方法的区别总结
-
调用方式
1> load是根据函数地址直接调用
2> initialize是通过objc_msgSend调用 -
调用时刻
1> load是runtime加载类、分类的时候调用(只会调用一次)
2>initialize是类第一次接收到消息的时候调用,每个类只会initialize一次(父类的initialize方法可能会被调用多次) -
在类和分类、子类中,load、initialize的调用顺序?
一、 load
1>先调用类的load
(a)先编译的类,优先调用load
(b) 调用子类的load之前,会先调用父类的load
2>再调用分类的load
(a) 先编译的分类,优先调用load二、 initialize
1> 先初始化父类
2>再初始化子类