美文网首页
每日一问07——+load&+initialize

每日一问07——+load&+initialize

作者: 巫师学徒 | 来源:发表于2017-09-05 14:41 被阅读22次

    简介:+ initialize 和 + load 是 NSObject 类的两个类方法,它们会在运行时自动调用。

    先看个例子

    @interface BaseClass : NSObject
    @end
    
    @implementation BaseClass
    + (void)initialize {
        NSLog(@"%@ , %s", [self class], __FUNCTION__);
    }
    
    @end
    
    @interface SubClass : BaseClass
    @end
    
    @implementation SubClass
    + (void)load {
        NSLog(@"SubClass load");
    }
    
    @end
    

    运行程序后,打印结果为SubClass load。说明程序启动会自动调用load,而不会调用+ (void)initialize

    再改进一下例子

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            BaseClass *class = [[BaseClass alloc] init];
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    在main函数里面添加BaseClass初始化操作。打印结果为SubClass load ,+[BaseClass initialize] 。先猜测一下,+load是会100%调用的,而+ initialize只会在这个类被使用的时候才被调用。

    + initialize

    先看看 NSObject Class Reference 中关于 +initialize
    的说明:

    1.+ (void)initialize 消息是在该类接收到其第一个消息之前调用。关于这里的第一个消息需要特别说明一下,对于 NSObjectruntime 机制而言,其在调用 NSObject+ (void)load 消息不被视为第一个消息,但是,如果像普通函数调用一样直接调用 NSObject+ (void)load 消息,则会引起 + (void)initialize 的调用。反之,如果没有向 NSObject 发送第一个消息,+ (void)initialize 则不会被自动调用。

    2.在应用程序的生命周期中,runtime 只会向每个类发送一次 + (void)initialize 消息,如果该类是子类,且该子类中没有实现 + (void)initialize 消息,或者子类显示调用父类实现 [super initialize], 那么则会调用其父类的实现。也就是说,父类的 + (void)initialize 可能会被调用多次。

    3.如果类包含分类,且分类重写了initialize方法,那么则会调用分类的initialize 实现,而原类的该方法实现不会被调用,这个机制同 NSObject 的其他方法(除 + (void)load 方法) 一样,即如果原类同该类的分类包含有相同的方法实现,那么原类的该方法被隐藏而无法被调用。

    4.父类的 initialize 方法先于子类的 initialize 方法调用。

    • 先改进一下例子
    @implementation BaseClass
    
    + (void)initialize {
    //    NSLog(@"%@ , %s", [self class], __FUNCTION__);
        NSLog(@"%s", __FUNCTION__);
    }
    
    @end
    
    @implementation SubClass
    
    + (void)initialize {
        NSLog(@"%s", __FUNCTION__);
    }
    
    @end
    
    int main(int argc, char * argv[]) {
        @autoreleasepool {
            SubClass *class = [[SubClass alloc] init];
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    打印结果为+[BaseClass initialize],+[SubClass initialize]。验证结果先调用了BaseClassinitialize后调用了SubClassinitialize

    • ViewController里测试
    @implementation ViewController
    
    - (void)viewWillAppear:(BOOL)animated {
        SubClass *class = [[SubClass alloc] init];
        NSLog(@"viewWillAppear");
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        SubClass *class = [[SubClass alloc] init];
        NSLog(@"viewDidLoad");
    }
    @end
    

    打印结果为 +[BaseClass initialize],+[SubClass initialize],viewDidLoad,viewWillAppear。说明runtime的确只会向每个类发送一次+ (void)initialize 消息。

    • 给SubClass添加一个分类试试
    @implementation SubClass (test)
    
    + (void)initialize {
        NSLog(@"%s", __FUNCTION__);
    }
    
    @end
    @implementation ViewController
    
    - (void)viewWillAppear:(BOOL)animated {
        SubClass *class = [[SubClass alloc] init];
        NSLog(@"viewWillAppear");
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        SubClass *class = [[SubClass alloc] init];
        NSLog(@"viewDidLoad");
    }
    @end
    

    打印结果是+[BaseClass initialize],+[SubClass(test) initialize],可以看出当分类实现+initialize后会覆盖掉原来类中的+initialize方法

    +load

    先看看 NSObject Class Reference 中关于 + (void)load
    的说明:

    1.+ (void)load 会在类或者类的分类添加到 Objective-c runtime 时调用,该调用发生在 application:willFinishLaunchingWithOptions: 调用之前调用。
    2.父类的 +load 方法先于子类的 +load 方法调用,类本身的 +load 方法先于分类的 +load 方法调用。

    为刚才的类添加+load方法实现

    @implementation SubClass
    
    + (void)initialize {
        NSLog(@"%s", __FUNCTION__);
    }
    
    + (void)load {
        NSLog(@"%@ %s", [self class], __FUNCTION__);
    }
    
    @end
    
    @implementation SubClass (test)
    
    + (void)initialize {
        NSLog(@"%s", __FUNCTION__);
    }
    
    + (void)load {
        NSLog(@"%@ %s", [self class], __FUNCTION__);
    }
    
    @end
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        // Override point for customization after application launch.
        NSLog(@"didFinishLaunchingWithOptions");
        return YES;
    }
    

    打印顺序为

    +[BaseClass initialize]
    BaseClass +[BaseClass load]
    +[SubClass(test) initialize]
    SubClass +[SubClass load]
    SubClass +[SubClass(test) load]
    didFinishLaunchingWithOptions

    从这个例子可以出,load方法不会被分类的+load覆盖,父类的load方法先调用,load方法在didFinishLaunchingWithOptions执行前调用。

    回到最开始的例子,

    @implementation BaseClass
    
    + (void)initialize {
    //    NSLog(@"%@ , %s", [self class], __FUNCTION__);
        NSLog(@"%s", __FUNCTION__);
    }
    @end
    @implementation SubClass
    
    + (void)load {
        NSLog(@"%@ %s", [self class], __FUNCTION__);
    }
    
    @end
    
    SubClass *class = [[SubClass alloc] init];
    

    打印顺序

    +[BaseClass initialize]
    +[BaseClass initialize]
    SubClass +[SubClass load]

    验证了当子类没有实现initialize时,父类的initialize调用了2次且 +initialize 的调用在 +load 调用之前,这是因为我们在 +load 实现中包含 [self class] 的调用。

    到这里,+ initialize+load的调用顺序与特性就验证的差不多了。那么知道了这些在开发的时候具体有什么用处呢?

    实际案例

    +load案例

    load使用示例1, 见 @sunnyxx 大神的博客 Notification Once, 用于给 AppDelegate 瘦身。
    load使用示例2, 见博客 Method Swizzling 和 AOP 实践, 在UIViewController的 +load 时期执行IMP替换,实现AOP。

    + initialize案例

    使用initialize与static实现单例模式:

    static SingleModel *initTest = nil;
    
    @implementation SingleModel
    
    + (void)initialize
    {
        NSLog(@"InitTest : initialize className : %@",[self class]);
        if (initTest == nil) {
            initTest = [[SingleModel alloc] init];
        }
    }
    + (SingleModel *)defaultManager
    {
        return initTest;
    }
    
    @end
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        SingleModel *single1 = [SingleModel defaultManager];
        SingleModel *single2 = [SingleModel defaultManager];
        SingleModel *single3 = [SingleModel defaultManager];
        NSLog(@"single1 %p",single1);
        NSLog(@"single2 %p",single2);
        NSLog(@"single3 %p",single3);
    }
    @end
    

    打印结果

    single1 0x60000000fb10
    single2 0x60000000fb10
    single3 0x60000000fb10

    都访问的同一块内存,只初始化了一次。
    同理我们也可以借助initialize初始化一些全局变量,静态变量。

    最后再补充一下:load和initialize方法内部使用了锁,因此它们是线程安全的。实现时要尽可能保持简单,避免阻塞线程,不要再使用锁。

    相关文章

    Objective-C类初始化:load与initialize
    NSObject +load and +initialize - What do they do?
    iOS初探+load和+initialize

    相关文章

      网友评论

          本文标题:每日一问07——+load&+initialize

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