1. load
结论:
- +(void)load方法无需手动调用,当类被runtime加载后,自动调用(如果实现的话)
- 调用顺序按照 [super class] -> [class] -> [sub class] -> [Category]
- 若有多个[child class],则按照compile source顺序,但要遵循先调用[super class]再调用[child class];
若有多个类别,则优先全部类,然后再类别,类别的顺序完全按照compile source顺序。 - 不遵循继承覆盖那套,基于2、3调用顺序,有就调用。若子类未写load,则不会调用父类的load。
- 正常情况下,load只会被runtime加载后调用一次。但是如果,我们人为写了[super load],父类的load方法会调用两次。
1.1 +(void)load方法无需手动调用,当类被runtime加载后,自动调用(如果实现的话)
新建 Person、Student两个类,Student继承Person;同时再新建Student+Category分类;
// Person类,实现load方法
@implementation Person
+(void)load {
NSLog(@"%s", __FUNCTION__);
}
@end
//Student类,实现load方法
@implementation Student
+(void)load {
NSLog(@"%s", __FUNCTION__);
}
@end
//Student+Category类别,实现load方法
@implementation Student (StudentCategory)
+(void)load {
NSLog(@"%s", __FUNCTION__);
}
@end
同时,我们在main写上打印
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
NSLog(@"main ...");
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
此时执行,输出:
2021-03-15 15:35:31.085844+0800 TestDemo[6593:77525] +[Person load]
2021-03-15 15:35:31.086350+0800 TestDemo[6593:77525] +[Student load]
2021-03-15 15:35:31.086437+0800 TestDemo[6593:77525] +[Student(StudentCategory) load]
2021-03-15 15:35:31.086577+0800 TestDemo[6593:77525] main ...
可以看出:
- 我们未做任何调用,自动调用了实现的load方法。
- main...打印在所有的load方法之后
Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
Declaration
- (void)load;
//苹果官方文档这样描述:
当一个类或分类被加载到runtime时被调用;实现此方法在加载时执行类的特定行为。
1.2 调用顺序按照 [super class] -> [class] -> [sub class] -> [Category]
基于1.1的打印,其实我们已经看出来,先调用了[person load] -> [Student load] -> [Student(StudentCategory) load]
此时,我们添加多个子类,看看效果如何。
//新建Teacher,继承自Person
@implementation Teacher
+(void)load {
NSLog(@"%s", __FUNCTION__);
}
@end
此时结果为:
2021-03-15 16:10:59.705768+0800 TestDemo[9791:115517] +[Person load]
2021-03-15 16:10:59.706308+0800 TestDemo[9791:115517] +[Teacher load]
2021-03-15 16:10:59.706414+0800 TestDemo[9791:115517] +[Student load]
2021-03-15 16:10:59.706484+0800 TestDemo[9791:115517] +[Student(StudentCategory) load]
2021-03-15 16:10:59.706602+0800 TestDemo[9791:115517] main ...
那为何Teacher先于Student调用load,这其中的顺序为何?
1.3 调用顺序
1.3.1 若有多个[child class],则按照compile source顺序,但要遵循先调用[super class]再调用[child class]
我们看下compile source顺序
此时Teacher.m位于第一位,那么我们调整下编译顺序,看下
此时输出: (可以看到,编译顺序的确影响了load的调用顺序。但是Person无论怎样,都先于child调用了。并且子类无法覆盖父类的load方法。)
2021-03-15 16:15:16.092491+0800 TestDemo[10185:120946] +[Person load]
2021-03-15 16:15:16.092981+0800 TestDemo[10185:120946] +[Student load]
2021-03-15 16:15:16.093099+0800 TestDemo[10185:120946] +[Teacher load]
2021-03-15 16:15:16.093292+0800 TestDemo[10185:120946] +[Student(StudentCategory) load]
2021-03-15 16:15:16.093470+0800 TestDemo[10185:120946] main ...
我们再加下码,新建一个StudentBoy,继承自Student。所以此时studentBoy的继承关系为 Person->Student->StudentBoy
此时的结果为:
2021-03-15 16:26:02.355757+0800 TestDemo[11200:136372] +[Person load]
2021-03-15 16:26:02.356303+0800 TestDemo[11200:136372] +[Student load]
2021-03-15 16:26:02.356396+0800 TestDemo[11200:136372] +[StudentBoy load]
2021-03-15 16:26:02.356494+0800 TestDemo[11200:136372] +[Teacher load]
2021-03-15 16:26:02.356581+0800 TestDemo[11200:136372] +[Student(StudentCategory) load]
2021-03-15 16:26:02.356762+0800 TestDemo[11200:136372] main ...
解释:
StudentBoy.m为第一个compile source文件。StudentBoy父类为Student,Student的父类又为Person。
所以依此打印了Person load -> Student load -> Person load。
即使Teacher.m和Student.m平级,且在Student.m之前,也不会改变这个顺序。
1.3.2 若有多个类别,则优先全部类,然后再类别,类别的顺序完全按照compile source顺序。
我们新增一个Teacher+Category类别
@implementation Teacher (TeacherCategory)
+(void)load {
NSLog(@"%s", __FUNCTION__);
}
@end
运行下
2021-03-15 17:51:11.270081+0800 TestDemo[22538:256893] +[Person load]_block_invoke
2021-03-15 17:51:11.270590+0800 TestDemo[22538:256893] +[Student load]
2021-03-15 17:51:11.270729+0800 TestDemo[22538:256893] +[Person initialize]
2021-03-15 17:51:11.270879+0800 TestDemo[22538:256893] +[Person initialize]
2021-03-15 17:51:11.271064+0800 TestDemo[22538:256893] +[StudentBoy load]
2021-03-15 17:51:11.271144+0800 TestDemo[22538:256893] +[Teacher load]
2021-03-15 17:51:11.271223+0800 TestDemo[22538:256893] +[Teacher(TeacherCategory) load]
2021-03-15 17:51:11.271307+0800 TestDemo[22538:256893] +[Student(StudentCategory) load]
2021-03-15 17:51:11.271455+0800 TestDemo[22538:256893] main ...
可以看到,所有的类load执行完了,才执行了category。可以多加几个category进行验证。
那同是category,顺序又如何呢?
上面的代码Compile source顺序为:
可以看到Teacher+Category先于Student+Category,那么我们调整下compile source顺序看看。
结果为:
2021-03-15 17:56:28.536222+0800 TestDemo[23323:265134] +[Person load]_block_invoke
2021-03-15 17:56:28.536641+0800 TestDemo[23323:265134] +[Student load]
2021-03-15 17:56:28.536759+0800 TestDemo[23323:265134] +[Person initialize]
2021-03-15 17:56:28.536831+0800 TestDemo[23323:265134] +[Person initialize]
2021-03-15 17:56:28.537031+0800 TestDemo[23323:265134] +[StudentBoy load]
2021-03-15 17:56:28.537108+0800 TestDemo[23323:265134] +[Teacher load]
2021-03-15 17:56:28.537197+0800 TestDemo[23323:265134] +[Student(StudentCategory) load]
2021-03-15 17:56:28.537268+0800 TestDemo[23323:265134] +[Teacher(TeacherCategory) load]
2021-03-15 17:56:28.537380+0800 TestDemo[23323:265134] main ...
可以得到一些结论:
- 无论category顺序如何调整,总是先执行了class的load,然后才是分类的。
- 在同是分类的级别里,Compile source在前的先执行。
1.3.3 若子类不写load,是否会调用父类的load?
我们屏蔽Student.m内的+(void)load方法。
此时结果为:
2021-03-15 18:18:34.252822+0800 TestDemo[26572:294490] +[Person load]
2021-03-15 18:18:34.253213+0800 TestDemo[26572:294490] +[Student(StudentCategory) load]
2021-03-15 18:18:34.253328+0800 TestDemo[26572:294490] +[StudentBoy load]
2021-03-15 18:18:34.253438+0800 TestDemo[26572:294490] +[Teacher load]
2021-03-15 18:18:34.253516+0800 TestDemo[26572:294490] +[Teacher(TeacherCategory) load]
2021-03-15 18:18:34.253649+0800 TestDemo[26572:294490] main ...
可以看到并未调用[Student load],但是有人会问,那不是还有 [Person load]打印了吗?
其实那不是Student调用的打印,如果Student会调用父类的,那 [Person load]应该会被打印两次才对。在后面的initilalize你会看到明显的区别。
1.4 正常情况下,load只会被runtime加载后调用一次。但是如果,我们人为写了[super load],父类的load方法会调用两次。
我们在Student的load方法内写入[super load],看看会发生什么
@implementation Student
+(void)load {
[super load]; //此处增加super load
NSLog(@"%s", __FUNCTION__);
}
@end
结果
2021-03-15 16:30:26.190810+0800 TestDemo[11595:140810] +[Person load]
2021-03-15 16:30:26.191265+0800 TestDemo[11595:140810] +[Person load]
2021-03-15 16:30:26.191399+0800 TestDemo[11595:140810] +[Student load]
2021-03-15 16:30:26.191509+0800 TestDemo[11595:140810] +[StudentBoy load]
2021-03-15 16:30:26.191600+0800 TestDemo[11595:140810] +[Teacher load]
2021-03-15 16:30:26.191676+0800 TestDemo[11595:140810] +[Student(StudentCategory) load]
2021-03-15 16:30:26.191812+0800 TestDemo[11595:140810] main ...
我们发现,[Person load]也会被调用两次。
那么,我们如何规避load有可能被调用两次?
@implementation Person
+(void)load {
//使用GCD的dispatch_once,让包裹内容只执行一次。
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"%s", __FUNCTION__);
});
}
@end
load用处
由于调用load方法时的环境很不安全,我们应该尽量减少load方法的逻辑。另一个原因是load方法是线程安全的,它内部使用了锁,所以我们应该避免线程阻塞在load方法中。
load常见于交换方法
@implementation Student
+(void)load {
NSLog(@"%s", __FUNCTION__);
static dispatch_once_t oneToken;
dispatch_once(&oneToken, ^{
[Student swizzleMethod:NSSelectorFromString(@"dealloc") byNewMethod:@selector(swizzleDealloc)];
});
}
//交换方法
+ (void)swizzleMethod:(SEL)originalSelector byNewMethod:(SEL)newSelector {
Class class = [self class];
Method originalMethod = class_getInstanceMethod(class, originalSelector);
Method newMethod = class_getInstanceMethod(class, newSelector);
bool didAdd = class_addMethod(class,
originalSelector,
method_getImplementation(newMethod),
method_getTypeEncoding(newMethod));
if (didAdd) {
class_replaceMethod(class,
newSelector,
method_getImplementation(originalMethod),
method_getTypeEncoding(originalMethod));
} else {
method_exchangeImplementations(originalMethod, newMethod);
}
}
- (void)dealloc{
NSLog(@"%s", __FUNCTION__);
}
- (void)swizzleDealloc{
NSLog(@"%s", __FUNCTION__);
[self swizzleDealloc];
}
@end
结果输出:
2021-03-15 17:40:37.872298+0800 TestDemo[20887:239992] +[Person load]_block_invoke
2021-03-15 17:40:37.872658+0800 TestDemo[20887:239992] +[Student load]
2021-03-15 17:40:37.872849+0800 TestDemo[20887:239992] +[StudentBoy load]
2021-03-15 17:40:37.872914+0800 TestDemo[20887:239992] +[Teacher load]
2021-03-15 17:40:37.872981+0800 TestDemo[20887:239992] +[Student(StudentCategory) load]
2021-03-15 17:40:37.873096+0800 TestDemo[20887:239992] main ...
2021-03-15 17:40:37.913371+0800 TestDemo[20887:239992] student >>>>>>
2021-03-15 17:40:37.913491+0800 TestDemo[20887:239992] -[Student swizzleDealloc]
2021-03-15 17:40:37.913596+0800 TestDemo[20887:239992] -[Student dealloc]
2021-03-15 17:40:37.913683+0800 TestDemo[20887:239992] student <<<<<<
可见,我们成功的使用了自定义的dealloc方法。
2. initialize
我们先看apple文档解释
Summary
Initializes the class before it receives its first message.
在类接受到第一个消息之前 (何为第一个消息?我们直到,runtime内,调用方法其实就是发送消息。所以可以认为调用第一个方法之前。)
结论:
- initialize在接收到第一个消息之前被触发(initialize属于懒加载,所以只要不调用方法,是不会触发initialize),无需显示调用,自动调用。
- 若子类未实现initialize,会默认继承父类的并执行一遍。
- Category会覆盖类中的initialize方法,当有多个Category,会执行Compile source最后一个category的initialize
2.1 initialize在接收到第一个消息之前被触发,无需显示调用,自动调用。
基于以上代码,分别有 Person.m、Student.m(继承Person)、Student+Category.m(Student分类)、StudentBoy.m(继承Student) 以上都实现了 + (void)initialize
@implementation Student
+ (void)initialize {
NSLog(@"%s", __FUNCTION__);
}
- (id)init {
self = [super init];
if (self) {
// Initialize self.
NSLog(@"init 方法被调用");
}
return self;
}
@end
//ViewController.m文件
//在viewController初始化一个局部变量
NSLog(@"StudentBoy >>>>>>");
[[StudentBoy alloc] init];
NSLog(@"StudentBoy <<<<<<");
结果
2021-03-15 18:39:39.684465+0800 TestDemo[29769:329186] +[StudentBoy load]
2021-03-15 18:39:39.684832+0800 TestDemo[29769:329186] +[Teacher load]
2021-03-15 18:39:39.684925+0800 TestDemo[29769:329186] +[Teacher(TeacherCategory) load]
2021-03-15 18:39:39.685040+0800 TestDemo[29769:329186] main ...
2021-03-15 18:39:39.734637+0800 TestDemo[29769:329186] StudentBoy >>>>>>
2021-03-15 18:39:39.734726+0800 TestDemo[29769:329186] +[Person initialize]
2021-03-15 18:39:39.734811+0800 TestDemo[29769:329186] +[Student(StudentCategory) initialize]
2021-03-15 18:39:39.734894+0800 TestDemo[29769:329186] +[StudentBoy initialize]
2021-03-15 18:39:39.734987+0800 TestDemo[29769:329186] init 方法被调用
2021-03-15 18:39:39.735073+0800 TestDemo[29769:329186] StudentBoy <<<<<<
- 可以回顾之前的load,先于main。 而initialize在调用init之前被调用。
- Student的initialize被+[Student(StudentCategory) initialize]覆盖
- 从父类->子类的顺序调用
2.2若子类不实现initialize不实现,会继承父类的,且执行一次。(所以经常在inialize内部要判断是哪个类,有可能是子类在调用)
我们删除 Student.m 以及 Student+Category.m内的 initialize。
并修改Person.m内的initialize,打印出当前类:
@implementation Person
+ (void)initialize {
NSLog(@"%s class=%@", __FUNCTION__, self);
}
@end
执行结果:
2021-03-15 18:45:43.107517+0800 TestDemo[30691:338134] +[StudentBoy load]
2021-03-15 18:45:43.108253+0800 TestDemo[30691:338134] +[Teacher load]
2021-03-15 18:45:43.108354+0800 TestDemo[30691:338134] +[Teacher(TeacherCategory) load]
2021-03-15 18:45:43.108562+0800 TestDemo[30691:338134] main ...
2021-03-15 18:45:43.151152+0800 TestDemo[30691:338134] StudentBoy >>>>>>
2021-03-15 18:45:43.151278+0800 TestDemo[30691:338134] +[Person initialize] class=Person
2021-03-15 18:45:43.151398+0800 TestDemo[30691:338134] +[Person initialize] class=Student
2021-03-15 18:45:43.151522+0800 TestDemo[30691:338134] +[StudentBoy initialize]
2021-03-15 18:45:43.151647+0800 TestDemo[30691:338134] init 方法被调用
2021-03-15 18:45:43.151760+0800 TestDemo[30691:338134] StudentBoy <<<<<<
可以看到 +[Person initialize]被调用了两次,然后但是class却不同。
initialize用途
一般initialize用来初始化静态变量和全局变量等,我们也可以利用initialize的线程安全,来实现单例,例如:
static Printer *instance = nil;
@implementation Printer
+ (void)initialize {
if (!instance) {
instance = [[Printer alloc] init];
}
}
+ (Printer *)instance {
return instance;
}
//other methods
@end