iOS load和initialize区别

1. load

结论:

  1. +(void)load方法无需手动调用,当类被runtime加载后,自动调用(如果实现的话)
  2. 调用顺序按照 [super class] -> [class] -> [sub class] -> [Category]
  3. 若有多个[child class],则按照compile source顺序,但要遵循先调用[super class]再调用[child class];
    若有多个类别,则优先全部类,然后再类别,类别的顺序完全按照compile source顺序。
  4. 不遵循继承覆盖那套,基于2、3调用顺序,有就调用。若子类未写load,则不会调用父类的load。
  5. 正常情况下,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 ...

可以看出:

  1. 我们未做任何调用,自动调用了实现的load方法。
  2. 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顺序


截屏2021-03-15 下午4.13.02.png

此时Teacher.m位于第一位,那么我们调整下编译顺序,看下


截屏2021-03-15 下午4.15.21.png

此时输出: (可以看到,编译顺序的确影响了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 下午4.25.28.png

此时的结果为:

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顺序为:


截屏2021-03-15 下午5.55.22.png

可以看到Teacher+Category先于Student+Category,那么我们调整下compile source顺序看看。


截屏2021-03-15 下午5.56.21.png

结果为:
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 ...

可以得到一些结论:

  1. 无论category顺序如何调整,总是先执行了class的load,然后才是分类的。
  2. 在同是分类的级别里,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内,调用方法其实就是发送消息。所以可以认为调用第一个方法之前。)


结论:

  1. initialize在接收到第一个消息之前被触发(initialize属于懒加载,所以只要不调用方法,是不会触发initialize),无需显示调用,自动调用。
  2. 若子类未实现initialize,会默认继承父类的并执行一遍。
  3. 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 <<<<<<
  1. 可以回顾之前的load,先于main。 而initialize在调用init之前被调用。
  2. Student的initialize被+[Student(StudentCategory) initialize]覆盖
  3. 从父类->子类的顺序调用

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容