initialize

作者: 迷路的安然和无恙 | 来源:发表于2017-11-18 21:48 被阅读100次

从初始化的类方法initialize开始:
为什么要说它?因为这个方法很懒。不同于load方法,在main函数执行前就已经执行,initialize方法只在类第一次被加载的时候调用。
以下是代码示例:

+ (void)initialize {
    [super initialize];
    NSLog(@"initialize");
}

- (instancetype)init {
    self = [super init];
    if (!self) return nil;
    NSLog(@"init");
    return self;
}
控制台打印如下: image.png

多次多当前类进行初始化,但是initialize方法,只在第一次创建该类的实例时调用了。但有一个疑问,那该类的子类在创建时,是否也不会再调用initialize方法了呢?

于是继续示例如下:

@interface SubViewController : ViewController

@end
//  Appdelegate代码如下

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    
    self.window.rootViewController = SubViewController.new;
    
    [self.window makeKeyAndVisible];
    
    return YES;
}

控制台信息如下: image.png

再创建一个SubViewController的子类,控制台信息如下:

image.png

现在,可以理解成,子类中的initialize方法也会执行一次,也可以理解成,父类的initialize被调用了三次。

疑问

如何保证该类或该类的子类只会调用一次initialize方法呢?测试代码如下:

+ (void)initialize {
    if (self == [ViewController self]) {
        // ... do the initialization ...
        NSLog(@"initialize");
    }
}
image.png

这样就保证了该类及其子类只会执行一次initialize
我对这个initialize方法其实是不熟悉的,基本未使用过。但是我好奇这个初始化方法,为什么不被使用,直到我了解了它的Apple文档,才明白它其实对于上层来说,并无太大作用,也不敢用。

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.

initialize是以阻塞方式调用的,该方法调用的时间,决定了初始化的时间。于是我用一下代码验证了该说法:

+ (void)initialize {
    if (self == [ViewController self]) {
        for (int i = 0; i < 1000000; i++) {
            NSLog(@"initialize");
        }
        
    }
}
示例如下: image.png 执行完成后的视图如下: image.png

所以苹果劝我们,如果调用该方法,一定不要在该方法中实现的太过复杂。

为什么是线程阻塞的方式

查看该方法的调用栈如下

image.png
直接来看调用栈中的 lookUpImpOrForward 方法,lookUpImpOrForward 方法只会在向对象发送消息,并且在类的缓存中没有找到消息的选择子时才会调用,lookUpImpOrForward 方法是 objc_msgSend 触发的。查阅资料之后,发现当前调用的方法是 alloc ,也就是说,initialize 方法是在 alloc 方法之前调用的,alloc 的调用导致了前者的执行。
但如何知道,当前类是否被初始化过?查阅资料显示,使用if (initialize && !cls->isInitialized())来判断当前类是否初始化过:
bool isInitialized() {
   return getMeta()->data()->flags & RW_INITIALIZED;
}

当前类是否初始化过的信息就保存在[元类]class_rw_t结构体中的 flags 中。

_class_initialize 方法

在 initialize 的调用栈中,直接调用其方法的是下面的这个 C 语言函数:

void _class_initialize(Class cls)
{
    Class supercls;
    BOOL reallyInitialize = NO;

    // 1. 强制父类先调用 initialize 方法
    supercls = cls->superclass;
    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
    {
        // 2. 通过加锁来设置 RW_INITIALIZING 标志位
        monitor_locker_t lock(classInitLock);
        if (!cls->isInitialized() && !cls->isInitializing()) {
            cls->setInitializing();
            reallyInitialize = YES;
        }
    }
    
    if (reallyInitialize) {
        // 3. 成功设置标志位,向当前类发送 +initialize 消息
        _setThisThreadIsInitializingClass(cls);

        ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);

        // 4. 完成初始化,如果父类已经初始化完成,设置 RW_INITIALIZED 标志位,
        //    否则,在父类初始化完成之后再设置标志位。
        monitor_locker_t lock(classInitLock);
        if (!supercls  ||  supercls->isInitialized()) {
            _finishInitializing(cls, supercls);
        } else {
            _finishInitializingAfter(cls, supercls);
        }
        return;
    } else if (cls->isInitializing()) {
        // 5. 当前线程正在初始化当前类,直接返回,否则,会等待其它线程初始化结束后,再返回
        if (_thisThreadIsInitializingClass(cls)) {
            return;
        } else {
            monitor_locker_t lock(classInitLock);
            while (!cls->isInitialized()) {
                classInitLock.wait();
            }
            return;
        }
    } else if (cls->isInitialized()) {
        // 6. 初始化成功后,直接返回
        return;
    } else {
        _objc_fatal("thread-safe class init in objc runtime is buggy!");
    }
}

方法的主要作用自然是向未初始化的类发送+initialize消息,不过会强制父类先发送+initialize

  1. 强制未初始化过的父类调用initialize方法

    if (supercls  &&  !supercls->isInitialized()) {
        _class_initialize(supercls);
    }
    
  2. 通过加锁来设置 RW_INITIALIZING标志位

    monitor_locker_t lock(classInitLock);
    if (!cls->isInitialized() && !cls->isInitializing()) {
        cls->setInitializing();
        reallyInitialize = YES;
    }
    
  3. 成功设置标志位、向当前类发送 +initialize消息

    ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
    
  4. 完成初始化,如果父类已经初始化完成,设置RW_INITIALIZED标志位。否则,在父类初始化完成之后再设置标志位

    monitor_locker_t lock(classInitLock);
    if (!supercls  ||  supercls->isInitialized()) {
        _finishInitializing(cls, supercls);
    } else {
        _finishInitializingAfter(cls, supercls);
    }
    
  5. 如果当前线程正在初始化当前类,直接返回,否则,会等待其它线程初始化结束后,再返回,保证线程安全

    if (_thisThreadIsInitializingClass(cls)) {
        return;
    } else {
        monitor_locker_t lock(classInitLock);
        while (!cls->isInitialized()) {
            classInitLock.wait();
        }
        return;
    }
    
  6. 初始化成功后,直接返回

    return;
    

管理初始化队列

因为我们始终要保证父类的初始化方法要在子类之前调用,所以我们需要维护一个PendingInitializeMap的数据结构来存储当前的类初始化需要哪个父类先初始化完成

image.png

这个数据结构中的信息会被两个方法改变:

if (!supercls  ||  supercls->isInitialized()) {
  _finishInitializing(cls, supercls);
} else {
  _finishInitializingAfter(cls, supercls);
}

分别是_finishInitializing以及_finishInitializingAfter,先来看一下后者是怎么实现的,也就是在父类没有完成初始化的时候调用的方法:

static void _finishInitializingAfter(Class cls, Class supercls)
{
    PendingInitialize *pending;
    pending = (PendingInitialize *)malloc(sizeof(*pending));
    pending->subclass = cls;
    pending->next = (PendingInitialize *)NXMapGet(pendingInitializeMap, supercls);
    NXMapInsert(pendingInitializeMap, supercls, pending);
}

因为当前类的父类没有初始化,所以会将子类加入一个数据结构PendingInitialize中,这个数据结构其实就类似于一个保存子类的链表。这个链表会以父类为键存储到pendingInitializeMap中。

NXMapInsert(pendingInitializeMap, supercls, pending);

而在父类已经调用了初始化方法的情况下,对应方法_finishInitializing的实现就稍微有些复杂了:

static void _finishInitializing(Class cls, Class supercls)
{
    PendingInitialize *pending;

    cls->setInitialized();

    if (!pendingInitializeMap) return;
    pending = (PendingInitialize *)NXMapGet(pendingInitializeMap, cls);
    if (!pending) return;

    NXMapRemove(pendingInitializeMap, cls);

    while (pending) {
        PendingInitialize *next = pending->next;
        if (pending->subclass) _finishInitializing(pending->subclass, cls);
        free(pending);
        pending = next;
    }
}

首先,由于父类已经完成了初始化,在这里直接将当前类标记成已经初始化,然后递归地将被当前类 block 的子类标记为已初始化,再把这些当类移除pendingInitializeMap


感谢译者:
@as_one
@Draveness
作者:
@Mattt Thompson

相关文章

网友评论

    本文标题:initialize

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