美文网首页代码改变世界iOS技术点基础
NSObject 的 initialize 和 load 方法

NSObject 的 initialize 和 load 方法

作者: 要上班的斌哥 | 来源:发表于2017-10-29 13:36 被阅读206次

    作为 NSObject 类中的 2 个方法 initialize 和 load 一直被我们所熟知,但是又没有具体去深入的了解,今天结合 Apple 官方文档,我们来深入了解一下 initialize 和 load 方法 。

    initialize

    看文档可以得知 initialize 方法的作用是在 class 收到第一个消息之前初始化 class。

    Initializes the class before it receives its first message.
    
    + (void)initialize;
    
    1. runtime 会在程序的 class 收到第一个消息之前给每一个 class 发送 initialize 消息,让 class 进行初始化。
    2. Superclasses 会在 Subclasses 之前收到 initialize 消息。
    3. initialize 方法是线程安全的,在 initialize 方法运行期间, class会被锁定,其他的线程无法向该 class 发送消息,所以我们尽量避免在 initialize 方法里面做复杂的实现。

    接下来我们用代码来探究 initialize 这个方法,我们有 3 个 class ,分别是 Man,Woman,Person。 其中 Man 和 Woman 都继承自 Person 。

    
    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    
    @end
    
    
    #import "Person.h"
    
    @implementation Person
    +(void)initialize{
        NSLog(@"call person initialize");
    }
    
    @end
    
    #import "Person.h"
    
    @interface Man : Person
    
    @end
    
    #import "Man.h"
    
    @implementation Man
    
    @end
    
    
    
    #import "Person.h"
    
    @interface Woman : Person
    
    @end
    
    #import "Woman.h"
    
    @implementation Woman
    
    @end
    
    

    在 main.m 方法代码如下:

    #import <Foundation/Foundation.h>
    #import "Person.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p = [[Person alloc] init];
        }
        return 0;
    }
    

    我们运行程序得到如下输出

     call person initialize
    

    在 Person 的 initialize 方法设置断点,查看堆栈调用,Person 调用的第一个方法确实是 initialize ,该方法在 Person 收到第一个消息之前初始化 Person。

    image.png

    接下来修改 main.m 的代码,同时生成 Person ,Man,Woman 的对象实例。

    #import <Foundation/Foundation.h>
    #import "Person.h"
    #import "Man.h"
    #import "Woman.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p = [[Person alloc] init];
            Man *m = [[Man alloc] init];
            Woman *w = [[Woman alloc] init];
        }
        return 0;
    }
    

    运行程序,查看控制台输出

     call person initialize
     call person initialize
     call person initialize
    

    我们可以看出如果子类没有实现 initialize 方法,runtime 会调用父类的 initialize 实现,那么我们就不用在子类中调用 [super initialize]
    ,同时也意味着父类的 initialize 会被多次调用,那么我们可以采用如下的方式来避免这个问题。

    + (void)initialize {
      if (self == [ClassName self]) {
        // ... do the initialization ...
      }
    }
    

    那如果 Person子类 Man 和 Woman 都实现 initialize 方法呢?

    @implementation Man
    +(void)initialize{
        NSLog(@"call man initialize");
    }
    
    +(void)initialize{
        NSLog(@"call woman initialize");
    }
    @end
    
    
    

    运行程序,查看控制台输出,可以看到 class 都是调用各自的 initialize 方法实现。

    call person initialize
    call man initialize
    call woman initialize
    

    每个 class 有且仅有一次 initialize 方法调用,如果想要实现 class 和 category 的分别独立初始化,我们应该使用 load 方法。

    load

    看文档可以得知 class 或者 category 被添加到 runtime 的时候,load 方法就会被调用。

    Invoked whenever a class or category is added to the Objective-C runtime; 
    implement this method to perform class-specific behavior upon loading.
    
    + (void)load;
    
    1. 和 initialize 方法类似,class 的 load 方法会在 superlcasses 的 load 方法调用之后被调用。
    2. category 的 load 方法会在 class 的 load 方法调用之后被调用。

    接下来我们用代码来探究 load 这个方法,还是用之前的例子,我们有 3 个 class ,分别是 Man,Woman,Person。 其中 Man 和 Woman 都继承自 Person 。

    Person 类实现了 load 方法

    
    //Person.m
    #import "Person.h"
    
    @implementation Person
    
    +(void)initialize{
        NSLog(@"call person initialize");
    }
    
    +(void)load{
        NSLog(@"call person load");
    }
    
    // main.m
    #import <Foundation/Foundation.h>
    #import "Person.h"
    #import "Man.h"
    #import "Woman.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p = [[Person alloc] init];
    
        }
        return 0;
    }
    
    @end
    

    运行程序,查看控制台输出,可以看出 load 方法调用在 initialize 方法之前。

    call person load
    call person initialize
    

    接下来修改 main.m 实现

    #import <Foundation/Foundation.h>
    #import "Person.h"
    #import "Man.h"
    #import "Woman.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *p = [[Person alloc] init];
            Man *m = [[Man alloc] init];
            Woman *w = [[Woman alloc] init];
        }
        return 0;
    }
    
    

    运行程序,查看控制台输出,可以看出子类没有实现 load 方法的时候,runtime 不会自动调用父类的 load 方法实现

    call person load
    call person initialize
    call man initialize
    call woman initialize
    

    接下来修改 Man.m 和 Woman.m 实现

    
    // Man.m
    #import "Man.h"
    
    @implementation Man
    +(void)initialize{
        NSLog(@"call man initialize");
    }
    
    +(void)load{
        NSLog(@"call man load");
    }
    @end
    
    // Woman.m
    #import "Woman.h"
    
    @implementation Woman
    
    +(void)initialize{
        NSLog(@"call woman initialize");
    }
    
    +(void)load{
        NSLog(@"call woman load");
    }
    
    @end
    

    运行程序,查看控制台输出,可以看出,我们没有在 Woman 和 Man 中显式调用父类的 load 方法,但是父类的 load 方法调用都在子类之前,这个和 initialize 方法是一样的,毕竟是要先有父类初始化,才会有子类初始化。

    call person load
    call woman load
    call man load
    call person initialize
    call man initialize
    call woman initialize
    

    接下来新建一个 Man 的 category 叫做 Man(Work),

    // Mam + Work.m
    #import "Man+Work.h"
    @implementation Man (Work)
    +(void)load{
        NSLog(@"call Man (Work) load");
    }
    @end
    

    运行程序,查看控制台输出,可以看出 category 的 load 方法调用总是在 class 的 load 方法调用之后。

    call man load
    call Man (Work) load
    call man initialize
    

    总结

    通过一些代码例子和官方文档,我们可以知道 initialize 方法的作用是在 class 收到第一个消息之前初始化 class。load 方法的调用时机是在 class 或者 category 被添加到 runtime 的时候。load 方法调用在 initialize 方法之前。

    参考

    1. https://developer.apple.com/documentation/objectivec/nsobject/1418639-initialize?preferredLanguage=occ
    2. https://developer.apple.com/documentation/objectivec/nsobject/1418815-load?language=objc
    3. http://zhangbuhuai.com/initialize-and-load-in-objective-c/

    相关文章

      网友评论

      • TroyZhang:例子有些问题,以文章中`load`章节的第一个例子说明下:
        ```
        // 代码
        @Implementation Man
        +(void)initialize{
        NSLog(@"%@ call man initialize",[self class]);
        }

        +(void)load{
        NSLog(@"%@ call man load",[self class]);
        }
        @EnD
        ```
        作者得出的结论:
        > 运行程序,查看控制台输出,可以看出 load 方法调用在 initialize 方法之后。

        -------------------------------------------------------------------------
        上述示例是有问题的,理论上`load`在`initialize`之前调用的,这里之所以打log时先输出`initialize`中的NSLog、后输出`load`中的NSLog,是因为`load`中的 `[self class]`的方法调用(会触发调用`initialize`)。

        所以正确的执行顺序是:load -> [self class] -> 触发调用initialize -> 打印initialize中的日志 -> 打印load中的日志。

        所以文章中的举例是有问题的([self class]调用造成的),可以断点调试一下。
        要上班的斌哥::kissing_heart: 你好,我重新试了一下,正如你所说,案例确实存在问题,文章已修改,谢谢!

      本文标题:NSObject 的 initialize 和 load 方法

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