关闭

初探+load和+initialize方法

549人阅读 评论(0) 收藏 举报
分类:

本文引用CaryaLiu的《iOS初探+load和+initialize》和sunnyxx的《Notification Once》的博客

+ initialize+ loadNSObject 类的两个类方法,它们会在运行时自动调用,我们可以利用其特性做一些初始化操作。

initialize初探

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

>
Initializes the class before it receives its first message.
>
The runtime sends initialize to each class in a program just before the class, or any class that inherits from it, is sent its first message from within the program. The runtime sends the initialize message to classes in a thread-safe manner. Superclasses receive this message before their subclasses. The superclass implementation may be called multiple times if subclasses do not implement initialize—the runtime will call the inherited implementation—or if subclasses explicitly call [super initialize]. If you want to protect yourself from being run multiple times, you can structure your implementation along these lines:
>

+ (void)initialize {

    if (self == [ClassName self]) {

      // ... do the initialization ...

    }

}


> initialize is invoked only once per class. If you want to perform independent initialization for the class and for categories of the class, you should implementload methods.

从上面的说明可以看出:

  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 方法调用。

示例:

@interface People : NSObject

@end

@implementation People

+ (void)initialize {

   NSLog(@"%s", __FUNCTION__);

}

@end

@interface Student : People

@end

@implementation Student

+ (void)initialize {

   NSLog(@"%s", __FUNCTION__);

}

@end

@implementation ViewController

- (void)viewDidLoad {

   [super viewDidLoad];

   Student *student = [[Student alloc] init];

}

@end

运行程序会输出什么? 答案如下:

  +[People initialize]

  +[Student initialize]

上面示例可以看出子类会调用父类的 + initialize 方法。

如果 ViewController 内的调用换成如下的方式, 控制台输出又会是什么呢?

@implementation ViewController

- (void)viewDidLoad {

   [super viewDidLoad];

   Student *student = [[Student alloc] init];

   NSLog(@"viewDidLoad finished.");

}

- (void)viewWillAppear:(BOOL)animated {

   [super viewWillAppear:animated];

   Student *student = [[Student alloc] init];

}

@end

输出如下:

  +[People initialize]

  +[Student initialize]

viewDidLoad finished.

上面示例可以看出,对于 runtime 而言,+ initialize 方法在程序生命周期内只会调用一次。

Student 添加分类如下:

@interface Student (Score)

@end

@implementation Student (Score)

+ (void)initialize {

   NSLog(@"%s", __FUNCTION__);

}

@end

调用的地方修改如下:

@implementation ViewController

- (void)viewDidLoad {

   [super viewDidLoad];

   Student *student = [[Student alloc] init];

}

@end

那么输出是什么呢? 如下:

  +[People initialize]

  +[Student(Score) initialize]

从该实例可以看出,分类的 +initialize 方法会覆盖原类中+initialize 方法。

load初探

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

> Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.> > The load message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond.>

The order of initialization is as follows:

  1. All initializers in any framework you link to.
  2. All +load methods in your image.
  3. All C++ static initializers and C/C++ attribute(constructor) functions in your image.
  4. All initializers in frameworks that link to you.

In addition:

  • A class’s +load method is called after all of its superclasses’ +load methods.
  • A category +load method is called after the class’s own +load method.

In a custom implementation of load you can therefore safely message other unrelated classes from the same image, but any load methods implemented by those classes may not have run yet.

从上面的描述可以看出:

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

看看下面的示例:

//父类

@interface People : NSObject

@end@implementation People

+ (void)initialize {

   NSLog(@"%@ , %s", [self class], __FUNCTION__);

}

+ (void)load {

   NSLog(@"%@, %s", [self class], __FUNCTION__);

}

@end

//子类

@interfaceStudent :People

@end

@implementationStudent

+ (void)initialize {

  NSLog(@"%@, %s", [self class], __FUNCTION__);

}

+ (void)load {

  NSLog(@"%@, %s", [self class], __FUNCTION__);

}

@end

//子类分类

@interfaceStudent (Score)

@end

@implementationStudent (Score)

+ (void)initialize {

  NSLog(@"%@, %s", [self class], __FUNCTION__);

}

+ (void)load {

  NSLog(@"%@ %s", [self class], __FUNCTION__);

}

@end

控制台输出为:

  People , +[People initialize]

  People, +[People load]

  Student, +[Student(Score) initialize]

  Student, +[Student load]

  Student +[Student(Score) load]

示例2:

//父类

@interfacePeople :NSObject

@end

@implementationPeople

+ (void)initialize {

  NSLog(@"%@ , %s", [self class], __FUNCTION__);

}

@end

//子类

@interfaceStudent :People

@end

@implementationStudent

+ (void)load {

  NSLog(@"%@, %s", [self class], __FUNCTION__);

}

@end

控制台输出为:

  People , +[People initialize]

  Student , +[People initialize]

  Student, +[Student load]

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

另外, 重写 +initialize+load 方法时,没有必要显示调用其父类的方法。

总结:

  +(void)load +(void)initialize
执行时机 在程序运行后立即执行 在类的方法第一次被调时执行
若自身未定义,是否沿用父类的方法?
类别中的定义 全都执行,但后于类中的方法 覆盖类中的方法,只执行一个

load的实际应用实例

每一个项目中在- application:didFinishLaunchingWithOptions:中的代码都只是为了在程序启动时获得一次调用机会,有些为某些模块的初始化工作。

对于这些模块的初始化工作可以利用Notification和load方式在自己的模块内部搞定以达到appdelegate的瘦身。

/// Module.m
+ (void)load
{
    __block id observer =
    [[NSNotificationCenter defaultCenter]
     addObserverForName:UIApplicationDidFinishLaunchingNotification
     object:nil
     queue:nil
     usingBlock:^(NSNotification *note) {
         [self setup]; // Do whatever you want
         [[NSNotificationCenter defaultCenter] removeObserver:observer];
     }];
}
解释:

  • + load方法在足够早的时间点被调用
  • block 版本的通知注册会产生一个__NSObserver *对象用来给外部 remove 观察者
  • block 对 observer 对象的捕获早于函数的返回,所以若不加__block,会捕获到 nil
  • 在 block 执行结束时移除 observer,无需其他清理工作
  • 这样,在模块内部就完成了在程序启动点代码的挂载

值得注意的是,通知是在- application:didFinishLaunchingWithOptions:调用完成后才发送的。
顺便提下给 AppDelegate 瘦身的建议:AppDelegate 作为程序级状态变化的 delegate,应该只做路由分发的作用,具体逻辑实现代码还是应该在分别的模块中,这个文件应该保持整洁,除了<UIApplicationDelegate>的方法外不应该出现其他方法。


load还可以结合Method Swizzling解决我们实际开发中诸多常规手段所无法解决的问题, 比如代码的插桩,Hook,Patch等等.

 
1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:19971次
    • 积分:444
    • 等级:
    • 排名:千里之外
    • 原创:23篇
    • 转载:10篇
    • 译文:0篇
    • 评论:1条