iOS - initialize 方法探索

[toc]

参考

initialize

load

code

// NSObject 有实现该方法
+ (void)initialize;

objc4源码解析

思路

既然 +initialize 方法会在类第一次接收到消息时被调用,

所以 objc_msgSend()方法内部必然有调用 +initialize

但是 objc_msgSend() 的C源码没有开源, 只提供了汇编代码。

我们只能通过其寻找方法的链路思考:

isa -> 类对象/元类对象, 查找方法, 调用; 若找不到, 查找父类 ↓

super_class -> 类对象/元类对象, 查找方法, 调用; 若找不到, 继续查找父类 ↓

...

猜想, 在查找方法这一步, 有可能调用了 +initialize

源码(部分)
// 获取类方法
Method class_getClassMethod(Class cls, SEL sel) {
    if (!cls  ||  !sel) return nil;
    return class_getInstanceMethod(cls->getMeta(), sel);
}

Method class_getInstanceMethod(Class cls, SEL sel) {
    if (!cls  ||  !sel) return nil;
    lookUpImpOrForward(nil, sel, cls, LOOKUP_RESOLVER); // 查找IMP
    return _class_getMethod(cls, sel);
}
// 查找方法
IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior) {
    // 未被初始化, 就调用
    if (slowpath((behavior & LOOKUP_INITIALIZE) && !cls->isInitialized())) {
        cls = initializeAndLeaveLocked(cls, inst, runtimeLock); // 
    }
    // ...
}

static Class initializeAndLeaveLocked(Class cls, id obj, mutex_t& lock) {
    return initializeAndMaybeRelock(cls, obj, lock, true);
}

static Class initializeAndMaybeRelock(Class cls, id inst, mutex_t& lock, bool leaveLocked) {
    // ...
    Class nonmeta = getMaybeUnrealizedNonMetaClass(cls, inst);
    initializeNonMetaClass(nonmeta);
}

void initializeNonMetaClass(Class cls) {
    supercls = cls->superclass;
    // 有父类, 且父类未初始化, 递归调用, 先初始化父类
    if (supercls  &&  !supercls->isInitialized()) {
        initializeNonMetaClass(supercls);
    }
    callInitialize(cls);
    // ... 
}

// objc-initialize.mm
void callInitialize(Class cls) {
    // 调用了 initialize
    ((void(*)(Class, SEL))objc_msgSend)(cls, @selector(initialize));
    asm("");
}
结论
调用方式

由系统自动调用, 不能手动调用

系统通过消息发送机制, objc_msgSend()

调用顺序

先调用父类的 +initialize, 再调用子类的 +initialize

(如果子没实现就调用父类的, 根类NSObject有实现, 保证了不会报错)

太乱不看
父类 / 分类

+initialize 是通过 objc_msgSend() 进行调用的, 所以有以下特点:

  1. initialize 遵循继承规则, 父类的 initialize 会比子类先执行;

    如果被发消息的类及其分类 都没有实现 initialize, 则会查找并调用其父类的 initialize, 这种情况父类的initialize会被调用多次。

  2. 分类覆盖主类, 如果被发消息的类的分类实现了+initialize 方法, 就会覆盖这个类中的实现。

调用时机

在main()函数之后, 在该类或其子类收到第一条消息之前;
当向该类发送的第一个消息, 一般是类消息首先调用, 常见的是alloc;

《Apple Document》
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. Superclasses receive this message before their subclasses.

调用次数

结论:

不一定只被调用一次;

若子类及其分类未实现 initialize, 父类的 initialize 会被调用多次;

但父类的initialize被调用多次, 并不代表父类被初始化多次, 仅仅是子类调用了父类的方法而已。

分析:

initialize 的调用与普通方法一样都是使用 runtime 的消息发送机制。

  1. 若第一次给某个子类发送消息, 初始化该子类前需先初始化其父类, 此时父类的initialize被调用1次; (若此前父类已被初始化, 则不会再调用, 但也已经被调用一次)

    消息机制会通过子类的isa找到子类的元类, 查找该元类的方法列表, 如果子类没有实现initialize, 会通过元类的super_class指针查找父元类, 然后查找并调用父元类的initialize, 导致父元类的initialize被调用两次; (类方法存储在元类的方法列表)

    而如果子类没有实现initialize, 会查找并调用父类的initialize, 导致父类的initialize被调用两次;

    所以通常要在方法内加一个判断, 确认当前要初始化的是不是本类, 防止因子类的调用而将原本只需执行一次的代码执行两次。

  1. 另外, 初始化子类前, 系统会自动初始化父类, 子类不要手动调用super, 否则父类的initialize会被调用多次。

《Apple Document》
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: Listing 1

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

调用必然性

不一定被调用;

initialize 是懒加载的, 如果程序一直没有给某个类或它的子类发送消息, 那么它永远不会被调用, 这一点有利于节省系统资源, 避免浪费。


应用场景

苹果提供 initialize方法, 就是给开发者使用的, 第一次使用这个类的时候做一些事情。

initialize方法一般用于初始化全局变量 或 静态变量。

无法在编译期设定的全局变量, 可以放在initialize方法中初始化。

安全性

线程安全 线程阻塞

运行期系统会确保initialize方法是在线程安全的环境中执行, 即只有执行initialize的那个线程可以操作类或类实例。其他线程都要先阻塞, 等待initialize执行完

《Apple Document》
The runtime sends the initialize message to classes in a thread-safe manner. That is, initialize is run by the first thread to send a message to a class, and any other thread that tries to send a message to that class will block until initialize completes.
Because initialize is called in a blocking manner, it’s important to limit method implementations to the minimum amount of work necessary possible. Specifically, any code that takes locks that might be required by other classes in their initialize methods is liable to lead to deadlocks. Therefore, you should not rely on initialize for complex initialization, and should instead limit it to straightforward, class local initialization.

面试题

和 init 的区别?
+ (void)initialize;  // 类方法, 每个类第一次被初始化的时候调用一次; (alloc)
- (instancetype)init;  // 对象方法, 每个对象初始化的时候调用一次;

initialize 的调用在 init 之前。

和 load 的区别

见【load - 面试题】

什么情况下initialize会被调用多次?

【见本文 - 调用次数】

initialize 初始化类指的是创建类对象?

应该是 [TBC]

你可能感兴趣的:(iOS - initialize 方法探索)