NSObject的前世今生iOS程序员

iOS 小知识-load()&initialize()

2017-07-02  本文已影响200人  HideOnBush

前言

之前在面试中被问到关于 loadinitialize,虽然之前在网上有看到过相关的资料,但是却没有仔细看过,也没有实际得在项目中使用过,所以被问到就一脸闷逼。。最近在看一些基础的知识,对于这两个方法也有点了解,所以做个整理。

介绍

这两个方法都是NSObject下用来初始化类的类方法。

//Swift
class func initialize()
//Objective-C
+ (void)load;
在类接受第一个消息前初始化这个类        
//Swift
class func load()
//Objective-C
+ (void)initialize;
每当类或者分类被添加到Objective-C运行时的时候调用;实现此方法在加载的时候执行类的特定的行为。    

load()

对于加入运行时的每个类以及分类来说,一定会调用这个方法,而且仅仅调用一次。
初始化的顺序如下:
注意:

问题:
load 方法的问题在于,在执行子类 load 方法之前, 必定会先执行所有父类的 load 方法, 如果还依赖了其他程序库,那么其他程序库里相关类对的 load 方法也必定会先执行。然而,根据某个给定的程序库,却无法判断出其中个各类的载入顺序。所以,在 load 方法中使用其他类是不安全的。

重要
对于自定义的 load 方法的实现的 Swift 类桥接到 Objective-C 是不会自动调用的。

扩展

我们知道OC 中有一个东西叫: Method Swizzling。我们可以通过它来做埋点的工作,当时这只是它的用法之一,它还有很多用法,这里就不一一说明了。使用Method Swizzling 来做埋点(用户行为统计 统计用户点击事件以及页面跳转等操作,上传到服务器做数据分析等操作)可以使得埋点代码与业务代码分离,可复用性好。这里需要用到 Runtime 方面的知识,这里就不细讲。在使用 Method Swizzling 的时候方法交换的操作是在 load 方法中进行的,在分类中重写 load 方法,而且Swizzling should always be done in +load.

There are two methods that are automatically invoked by the Objective-C runtime for each class. +load is sent when the class is initially loaded, while +initialize is called just before the application calls its first method on that class or an instance of that class. Both are optional, and are executed only if the method is implemented.

Because method swizzling affects global state, it is important to minimize the possibility of race conditions. +load is guaranteed to be loaded during class initialization, which provides a modicum of consistency for changing system-wide behavior. By contrast, +initialize provides no such guarantee of when it will be executed—in fact, it may never be called, if that class is never messaged directly by the app.

load 方法总是会在类初始化的时候被调用,这给改变系统范围的操作提供了保障,然而 initialize 方法却不能保证一定会被调用,它可能永远不会被调用如果类一个不被使用(懒加载)。所以 Swizzling总是应该写在 load 方法里。

代码如下:

#import <objc/runtime.h>

@implementation UIViewController (Tracking)
//重写load 方法,Swizzling应该总是写在load方法里
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        //获取两个selector
        SEL originalSelector = @selector(viewWillAppear:);
        SEL swizzledSelector = @selector(xxx_viewWillAppear:);
        
        //通过selector获取方法
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);

        // When swizzling a class method, use the following:
        // Class class = object_getClass((id)self);
        // ...
        // Method originalMethod = class_getClassMethod(class, originalSelector);
        // Method swizzledMethod = class_getClassMethod(class, swizzledSelector);
        
        //这里的操作是判断类中是否已经有需要替换的方法的实现。
        BOOL didAddMethod =
            class_addMethod(class,
                originalSelector,
                method_getImplementation(swizzledMethod),
                method_getTypeEncoding(swizzledMethod));
        //如果类中已经有了需要替换的方法的实现,那么就将需要将替换原方法实现的实现替换为原方法的实现(解释起来听绕口的。。233333),没有就直接交换两个方法的实现。
        if (didAddMethod) {
            class_replaceMethod(class,
                swizzledSelector,
                method_getImplementation(originalMethod),
                method_getTypeEncoding(originalMethod));
        } else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

#pragma mark - Method Swizzling
//这里的方法来替换原来viewWillAppear,在这个方法里写上埋点的代码
- (void)xxx_viewWillAppear:(BOOL)animated {
    [self xxx_viewWillAppear:animated];
    NSLog(@"viewWillAppear: %@", self);
}

@end

参考资料:

initialize()

运行时系统会在这个类或者是继承于这个类的子类接受第一条消息之前向这个类发送 initialize() 方法。父类在子类之前接受这个消息。

运行时系统通过一种线程安全的方式向类发送initialize消息。也就是说,initialize方法是在发送给类的第一个消息的第一个线程中执行的,其他的线程只有等到initialize 方法执行完成后,才能继续向这个类发送消息。

父类实现的initialize 方法可以会被多次调用如果子类没有实现 initialize 方法——运行时系统会调用继承的方法实现或者是子类明确调用了 [super initialize]. 如果你想保护你自己避免调用多次,你可以按照下面的方法来实现你的initialize 方法:

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

因为initialize()是以堵塞的方式调用,所以限制类所需要做的事情就很有必要。尤其是任何需要用到锁的代码在其他类的initialize 中调用可能造成死锁。因此,你不能依靠 initialize 方法来做复杂的初始化,应该在使其变得简单,初始化类的本地变量。

特别需要注意的点

initialize() 每个类只会调用一次。如果你给类或者分类做一些独立的初始化,那么你应该去实现 load() 方法。

总结

本文是对于Effective Objective-C 2.0 这本书的部分总结,参考了苹果官方文档以及一些大神的博客。如果不对的地方请指出。蟹蟹。欢迎交流。

参考资料:

上一篇下一篇

猜你喜欢

热点阅读