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>