美文网首页
iOS--+load 和 +initialize

iOS--+load 和 +initialize

作者: mayuee | 来源:发表于2021-07-12 01:51 被阅读0次

只有父类实现

//父类 Aload
@implementation ALoad

+ (void)load
{
    NSLog(@"%s", __func__);
}

+(void)initialize
{
    NSLog(@"%s", __func__);
}

@end
//子类Bload
@interface BLoad : ALoad

@end
@implementation BLoad
//没有实现 load 和  initialize
@end

执行 BLoad *b = [[BLoad alloc] init];
打印结果

 +[ALoad load]
 +[ALoad initialize]
 +[ALoad initialize]

子类,父类都实现

//子类BLoad
@implementation BLoad

+ (void)load
{
    NSLog(@"%s", __func__);
}

+(void)initialize
{
    NSLog(@"%s", __func__);
}

@end

+[ALoad load]
+[BLoad load]
+[ALoad initialize]
+[BLoad initialize]
子类分类实现
@implementation BLoad (Cat)

+ (void)load
{
    NSLog(@"%s", __func__);
}

+(void)initialize
{
    NSLog(@"%s", __func__);

}

@end

打印结果

+[ALoad load]
+[BLoad load]
+[BLoad(Cat) load]
+[ALoad initialize]
+[BLoad(Cat) initialize]
父类分类也实现
@implementation ALoad (Cat)
+ (void)load
{
    NSLog(@"%s", __func__);
}

+(void)initialize
{
    NSLog(@"%s", __func__);
}
@end

打印结果

+[ALoad load]
+[BLoad load]
+[ALoad(Cat) load]
+[BLoad(Cat) load]
+[ALoad(Cat) initialize]
+[BLoad(Cat) initialize]
多一次初始化执行
BLoad *b = [[BLoad alloc] init];
    NSLog(@"------分割线------");
    BLoad *b1 = [[BLoad alloc] init];
    NSLog(@"------结束线------");

打印结果

+[ALoad load]
+[BLoad load]
+[ALoad(Cat) load]
+[BLoad(Cat) load]
main     //main函数打印
 +[ALoad(Cat) initialize]
+[BLoad(Cat) initialize]
------分割线------
 ------结束线------
没有Bload初始化,直接运行

打印结果

+[ALoad load]
 +[BLoad load]
+[ALoad(Cat) load]
 +[BLoad(Cat) load]
main     //main函数打印
两个子类
@interface CLoad : ALoad
//没有实现 load 和 initialize
@end
打印结果
+[ALoad load]
 +[BLoad load]
 +[ALoad(Cat) load]
+[BLoad(Cat) load]
 main
 +[ALoad initialize]
 +[BLoad initialize]
 +[ALoad initialize]

+load 和+initialize 区别在于调用方式和调用时刻不同

  1. 调用方式不同:
    +load是根据函数地址直接调用;
    initialize是通过objc_msgSend调用
  2. 调用时刻不同:
    +load方法会在runtime加载类、分类时,在main() 函数之前调用(而且程序运行过程中只调用一次)。
    +initialize是类第一次接收到消息的时候调用(即是类调用alloc时),每一个类只会initialize一次(下面提到子类没有实现+initialize,会调用父类的+initialize。这样父类的initialize方法可能会被调用多次)
  3. 调用顺序:
  • +load 方法调用优先级:父类>子类>分类,并且不会被覆盖,均会调用。先编译的分类的+load方法会被优先调。父类+load优先于子类。
  • +initialize 调用优先级:分类>父类>子类。由于+initialize是objc_msgSend机制,+initialize在所有分类要优先于类调用。 在类中 +initialize父类优先于子类 。
    • 如果分类和父类均实现了 +initialize,则只有分类的 +initialize 会被调用;
    • 如果父类和子类均实现了 +initialize,第一次引用子类时,先调用父类的 +initialize,再调用子类的 +initialize;
    • 如果父类实现了 +initialize 而子类没有实现,则第一次引用子类时,会调用两次父类的 +initialize ;如果多个子类没有实现+initialize,第一次引用一个子类的时候会调用两次父类的 +initialize,再次引用这个子类则不会再调用 initialize,而其他子类第一次引用时会调用父类的+initialize,所以同一个父类的+initialize可能会被调用多次。
  1. 均无须显式调用 super 方法
    +load 方法
    +load 方法的加载时机
    苹果文档描述如下
    当 class 或者 category 添加到 runtime 时被唤醒。即 +load 是在这个文件被程序加载时调用,因此 +load 方法是在 main 函数以前调用
  • load 方法的使用场景
    由于 +load 方法在 App 启动加载的时候调用,调用环境很不安全,此时不能保证所有的类被加载完成,应尽量减少load方法的逻辑。
    +load 方法是线程安全的,因为内部有锁,但是也带了一定的性能开销。所以一般会在 +load 方法中实现 Method Swizzle
    +load 方法是直接使用函数指针调用,即走 C 语言函数调用的流程,不是发送消息,并不会走消息转发流程,也就是说如果一个类实现了 load 函数就会调用,如果没有实现也不会调用该类的父类 load 函数。
    load方法通常用来进行Method Swizzle

+initialize 方法

  • initialize 方法加载时机
    这个函数是懒加载,只有当类接收了第一个消息的时候才会调用 initialize 方法,否则一直不会调用
  • initialize 方法的调用顺序
    分类->父类->子类,分类会覆盖原类,如果子类没有实现 initialize 方法,父类会调用两次
  1. 子类实现了 initialize,会先调用父类 initialize,再调用子类 initialize
  2. 子类没有实现 initialize,父类 initialize 方法会调用两次
  3. 如果先引用父类的实例对象,再引用子类实例对象,则会在引用父类实例对象时调用父类 initialize 方法;当引用子类实例对象时,由于父类的 initialize 方法已经执行,所以此时只调用子类 initialize 方法
  4. 如果先引用子类的实例对象,再引用父类的实例对象,则会在引用子类的实例对象时,在调用 initialize 方法前,先调用父类 initialize 方法,再调用子类的 initialize 方法;当引用父类实例对象时,由于在引用子类实例对象时已经调用了 initialzie 方法了,此时不再调用 initialize 方法
    由于 initialzie 方法可能会被调用多次,所以为保证 initialize 方法只被调用一次,苹果建议
  • (void)initialize {
    if (self == [ClassName self]) {
    // ... do the initialization ...
    }
    }
    或者使用 dispatch_once
  • initialize 方法使用场景
    initialize 是线程安全的,有可能阻塞线程,所以 initialize 方法应该限制做一些简单不复杂的类初始化的前期准备工作,比如,初始化全局变量或静态变量。NSMutableArray这种类型的实例化依赖于runtime的消息发送
  • initialize 方法实现原理
    打断点查看 initialize 方法的调用栈
    在调用 _class_initialize 函数之前,调用了 lookUpImpOrForward 函数
    该函数调用了 _class_initialize 函数
    _class_initialize 函数中调用了 callInitialize 函数,其中的调用顺序是从父类开始,到子类的,并且根据 if (supercls && !supercls->isInitialized()) 来看,如果父类已经调用过 initialize 函数,则父类不会再次调用 initialize 函数,对应了前文方法调用顺序中的 1、3,if (!cls->isInitialized() && !cls->isInitializing()) 对应前文调用顺序中的 4
    我们继续看下 callInitialize 函数做了什么:
    void callInitialize(Class cls)
    {
    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    asm("");
    }
    调用Initialize 函数的工作简单,就是发送消息。这是和 load 函数实现不一样的地方。load 函数的调用直接是函数指针的调用,而 initialize 函数是消息转发。所以 class 的子类就算没有实现 initialize 函数,也会调用父类的 initialize 函数。对应了前文调用顺序中的 2
image.png image.png

相关文章

网友评论

      本文标题:iOS--+load 和 +initialize

      本文链接:https://www.haomeiwen.com/subject/xmmnultx.html