Objective-C中懒惰的 initialize 方法
dongjie001
8年前
<h2>写在前面</h2> <p>这篇文章可能是对 Objective-C 源代码解析系列文章中最短的一篇了,在 Objective-C 中,我们总是会同时想到 <code>load</code>、<code>initialize</code> 这两个类方法。而这两个方法也经常在一起比较:</p> <p>在上一篇介绍 <code>load</code> 方法的<a href="http://www.open-open.com/lib/view/open1462086168417.html">文章</a>中,已经对 <code>load</code> 方法的调用时机、调用顺序进行了详细地分析,所以对于 <code>load</code> 方法,这里就不在赘述了。</p> <p>这篇文章会假设你知道:假设你是 iOS 开发者。</p> <p>本文会主要介绍:</p> <ol> <li><code>initialize</code> 方法的调用为什么是惰性的</li> <li>这货能干啥</li> </ol> <h2>initialize 的调用栈</h2> <p>在分析其调用栈之前,首先来解释一下,什么是惰性的。</p> <p>这是 <code>main.m</code> 文件中的代码:</p> <pre> <code class="language-objectivec">#import <Foundation/Foundation.h> @interface XXObject : NSObject @end @implementation XXObject + (void)initialize { NSLog(@"XXObject initialize"); } @end int main(int argc, const char * argv[]) { @autoreleasepool { } return 0; } </code></pre> <p>主函数中的代码为空,如果我们运行这个程序:</p> <p><img alt="Objective-C中懒惰的 initialize 方法" src="https://simg.open-open.com/show/317940b3afb274dc24bf1ea7d7598503.jpg" width="710" height="462"></p> <p>你会发现与 <code>load</code> 方法不同的是,虽然我们在 <code>initialize</code> 方法中调用了 <code>NSLog</code>。但是程序运行之后没有任何输出。</p> <p>如果,我们在自动释放池中加入以下代码:</p> <pre> <code class="language-objectivec">int main(int argc, const char * argv[]) { @autoreleasepool { __unused XXObject *object = [[XXObject alloc] init]; } return 0; } </code></pre> <p>再运行程序:</p> <p><img alt="Objective-C中懒惰的 initialize 方法" src="https://simg.open-open.com/show/564b75eaf6b3fc820cc610b2436f8ea4.jpg" width="1043" height="769"></p> <p>你会发现,虽然我们没有直接调用 <code>initialize</code> 方法。但是,这里也打印出了 <code>XXObject initialize</code> 字符串。</p> <blockquote> <p><code>initialize</code> <strong>只会在对应类的方法第一次被调用时,才会调用</strong>。</p> </blockquote> <p>我们在 <code>initialize</code> 方法中打一个断点,来查看这个方法的调用栈:</p> <p><img alt="Objective-C中懒惰的 initialize 方法" src="https://simg.open-open.com/show/13c38afb84b7dfa47d4aa75c13381cbc.jpg" width="1069" height="795"></p> <pre> <code class="language-objectivec">0 +[XXObject initialize] 1 _class_initialize 2 lookUpImpOrForward 3 _class_lookupMethodAndLoadCache3 4 objc_msgSend 5 main 6 start </code></pre> <p>直接来看调用栈中的 <code>lookUpImpOrForward</code> 方法,<code>lookUpImpOrForward</code> 方法<strong>只会在向对象发送消息,并且在类的缓存中没有找到消息的选择子时</strong>才会调用,具体可以看这篇文章,<a href="http://www.open-open.com/lib/view/open1461650361464.html">从源代码看 ObjC 中消息的发送</a>。</p> <p>在这里,我们知道 <code>lookUpImpOrForward</code> 方法是 <code>objc_msgSend</code> 触发的就够了。</p> <p><img alt="Objective-C中懒惰的 initialize 方法" src="https://simg.open-open.com/show/44da55e5ce2357ee0323bacc93b123e9.jpg" width="1330" height="999"></p> <p>在 lldb 中输入 <code>p sel</code> 打印选择子,会发现当前调用的方法是 <code>alloc</code> 方法,也就是说,<code>initialize</code> 方法是在 <code>alloc</code> 方法之前调用的,<code>alloc</code> 的调用导致了前者的执行。</p> <p>其中,使用 <code>if (initialize && !cls->isInitialized())</code> 来判断当前类是否初始化过:</p> <pre> <code class="language-objectivec">bool isInitialized() { return getMeta()->data()->flags & RW_INITIALIZED; } </code></pre> <blockquote> <p>当前类是否初始化过的信息就保存在<a href="/misc/goto?guid=4959672158121979066">元类</a>的 <code>class_rw_t</code> 结构体中的 <code>flags</code> 中。</p> </blockquote> <p>这是 <code>flags</code> 中保存的信息,它记录着跟当前类的元数据,其中第 16-31 位有如下的作用:</p> <p><img alt="Objective-C中懒惰的 initialize 方法" src="https://simg.open-open.com/show/90155e4b8b257c32a9abe793bfdcca04.png" width="840" height="555"></p> <p><code>flags</code> 的第 29 位 <code>RW_INITIALIZED</code> 就保存了当前类是否初始化过的信息。</p> <h2>_class_initialize 方法</h2> <p>在 <code>initialize</code> 的调用栈中,直接调用其方法的是下面的这个 C 语言函数:</p> <pre> <code class="language-objectivec">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!"); } } </code></pre> <p>方法的主要作用自然是向未初始化的类发送 <code>+initialize</code> 消息,不过会强制父类先发送 <code>+initialize</code>。</p> <ol> <li> <p>强制<strong>未初始化过的</strong>父类调用 <code>initialize</code> 方法</p> <pre> <code class="language-objectivec">if (supercls && !supercls->isInitialized()) { _class_initialize(supercls); } </code></pre> </li> <li> <p>通过加锁来设置 <code>RW_INITIALIZING</code> 标志位</p> <pre> <code class="language-objectivec">monitor_locker_t lock(classInitLock); if (!cls->isInitialized() && !cls->isInitializing()) { cls->setInitializing(); reallyInitialize = YES; } </code></pre> </li> <li> <p>成功设置标志位、向当前类发送 <code>+initialize</code> 消息</p> <p> </p> <code>objectivec ((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize); </code></li> <li> <p>完成初始化,如果父类已经初始化完成,设置 <code>RW_INITIALIZED</code> 标志位。否则,在父类初始化完成之后再设置标志位</p> <pre> <code class="language-objectivec">monitor_locker_t lock(classInitLock); if (!supercls || supercls->isInitialized()) { _finishInitializing(cls, supercls); } else { _finishInitializingAfter(cls, supercls); } </code></pre> </li> <li> <p>如果当前线程正在初始化当前类,直接返回,否则,会等待其它线程初始化结束后,再返回,<strong>保证线程安全</strong></p> <pre> <code class="language-objectivec">if (_thisThreadIsInitializingClass(cls)) { return; } else { monitor_locker_t lock(classInitLock); while (!cls->isInitialized()) { classInitLock.wait(); } return; } </code></pre> </li> <li> <p>初始化成功后,直接返回</p> <pre> <code class="language-objectivec">return; </code></pre> </li> </ol> <h2>管理初始化队列</h2> <p>因为我们始终要保证父类的初始化方法要在子类之前调用,所以我们需要维护一个 <code>PendingInitializeMap</code> 的数据结构来存储<strong>当前的类初始化需要哪个父类先初始化完成</strong>。</p> <p><img alt="Objective-C中懒惰的 initialize 方法" src="https://simg.open-open.com/show/187a6181006521360d4e70f0c0ed2f50.png" width="785" height="497"></p> <p>这个数据结构中的信息会被两个方法改变:</p> <pre> <code class="language-objectivec">if (!supercls || supercls->isInitialized()) { _finishInitializing(cls, supercls); } else { _finishInitializingAfter(cls, supercls); } </code></pre> <p>分别是 <code>_finishInitializing</code> 以及 <code>_finishInitializingAfter</code>,先来看一下后者是怎么实现的,也就是<strong>在父类没有完成初始化的时候</strong>调用的方法:</p> <pre> <code class="language-objectivec">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); } </code></pre> <p>因为当前类的父类没有初始化,所以会将子类加入一个数据结构 <code>PendingInitialize</code> 中,这个数据结构其实就类似于一个保存子类的链表。这个链表会以父类为键存储到 <code>pendingInitializeMap</code> 中。</p> <pre> <code class="language-objectivec">NXMapInsert(pendingInitializeMap, supercls, pending); </code></pre> <p>而在<strong>父类已经调用了初始化方法</strong>的情况下,对应方法 <code>_finishInitializing</code> 的实现就稍微有些复杂了:</p> <pre> <code class="language-objectivec">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; } } </code></pre> <p>首先,由于父类已经完成了初始化,在这里直接将当前类标记成已经初始化,然后<strong>递归地将被当前类 block 的子类标记为已初始化</strong>,再把这些当类移除 <code>pendingInitializeMap</code>。</p> <h2>小结</h2> <p>到这里,我们对 <code>initialize</code> 方法的研究基本上已经结束了,这里会总结一下关于其方法的特性:</p> <ol> <li><code>initialize</code> 的调用是惰性的,它会在第一次调用当前类的方法时被调用</li> <li>与 <code>load</code> 不同,<code>initialize</code> 方法调用时,所有的类都<strong>已经加载</strong>到了内存中</li> <li><code>initialize</code> 的运行是线程安全的</li> <li>子类会<strong>继承</strong>父类的 <code>initialize</code> 方法</li> </ol> <p>而其作用也非常局限,一般我们只会在 <code>initialize</code> 方法中进行一些常量的初始化。</p> <h2>参考资料</h2> <ul> <li><a href="/misc/goto?guid=4959672158121979066">What is a meta-class in Objective-C?</a></li> <li><a href="/misc/goto?guid=4959672153243540679">NSObject +load and +initialize - What do they do?</a></li> </ul> <p>来源:http://draveness.me/initialize/</p>