软件发布| 专题库| 优优资讯| 苹果专区| 安卓专区| 软件下载| 首页
优优资讯 电脑教程 安卓教程 安卓攻略 苹果教程 苹果攻略 新闻资讯

Objective-C Autorelease Pool 的实现原理(2)

时间:2015-06-23 来源:本站整理 我要评论
   不得不说,苹果对 @autoreleasepool {} 的实现真的是非常巧妙,真正可以称得上是代码的艺术。苹果通过声明一个 __AtAutoreleasePool 类型的局部变量 __autoreleasepool 来实现 @autoreleasepool {} 。当声明 __autoreleasepool 变量时,构造函数 __AtAutoreleasePool() 被调用,即执行 atautoreleasepoolobj = objc_autoreleasePoolPush(); ;当出了当前作用域时,析构函数 ~__AtAutoreleasePool() 被调用,即执行 objc_autoreleasePoolPop(atautoreleasepoolobj); 。也就是说 @autoreleasepool {} 的实现代码可以进一步简化如下:
 
  /* @autoreleasepool */ {
  void *atautoreleasepoolobj = objc_autoreleasePoolPush();
  // 用户代码,所有接收到 autorelease 消息的对象会被添加到这个 autoreleasepool 中
  objc_autoreleasePoolPop(atautoreleasepoolobj);
  }
 
  因此,单个 autoreleasepool 的运行过程可以简单地理解为 objc_autoreleasePoolPush()、[对象 autorelease] 和 objc_autoreleasePoolPop(void *) 三个过程。
 
  push 操作
 
  上面提到的 objc_autoreleasePoolPush() 函数本质上就是调用的 AutoreleasePoolPage 的 push 函数。
 
  void *
  objc_autoreleasePoolPush(void)
  {
  if (UseGC) return nil;
  return AutoreleasePoolPage::push();
  }
 
  因此,我们接下来看看 AutoreleasePoolPage 的 push 函数的作用和执行过程。一个 push 操作其实就是创建一个新的 autoreleasepool ,对应 AutoreleasePoolPage 的具体实现就是往 AutoreleasePoolPage 中的 next 位置插入一个 POOL_SENTINEL ,并且返回插入的 POOL_SENTINEL 的内存地址。这个地址也就是我们前面提到的 pool token ,在执行 pop 操作的时候作为函数的入参。
 
  static inline void *push()
  {
  id *dest = autoreleaseFast(POOL_SENTINEL);
  assert(*dest == POOL_SENTINEL);
  return dest;
  }
  push 函数通过调用 autoreleaseFast 函数来执行具体的插入操作。
 
  static inline id *autoreleaseFast(id obj)
  {
  AutoreleasePoolPage *page = hotPage();
  if (page && !page->full()) {
  return page->add(obj);
  } else if (page) {
  return autoreleaseFullPage(obj, page);
  } else {
  return autoreleaseNoPage(obj);
  }
  }
  autoreleaseFast 函数在执行一个具体的插入操作时,分别对三种情况进行了不同的处理:
 
  当前 page 存在且没有满时,直接将对象添加到当前 page 中,即 next 指向的位置;
 
  当前 page 存在且已满时,创建一个新的 page ,并将对象添加到新创建的 page 中;
 
  当前 page 不存在时,即还没有 page 时,创建第一个 page ,并将对象添加到新创建的 page 中。
 
  每调用一次 push 操作就会创建一个新的 autoreleasepool ,即往 AutoreleasePoolPage 中插入一个 POOL_SENTINEL ,并且返回插入的 POOL_SENTINEL 的内存地址。
 
  autorelease 操作
 
  通过 NSObject.mm 源文件,我们可以找到 -autorelease 方法的实现:
  1  - (id)autorelease {
  2    return ((id)self)->rootAutorelease();
  3 }
  通过查看 ((id)self)->rootAutorelease() 的方法调用,我们发现最终调用的就是 AutoreleasePoolPage 的 autorelease 函数。
  1  __attribute__((noinline,used))
  2  id
  3  objc_object::rootAutorelease2()
  4  {
  5    assert(!isTaggedPointer());
  6    return AutoreleasePoolPage::autorelease((id)this);
  7  }
  8   AutoreleasePoolPage 的 autorelease 函数的实现对我们来说就比较容量理解了,它跟 push 操作的实现非常相似。只不过 push 操作插入的是一个 POOL_SENTINEL ,而 autorelease 操作插入的是一个具体的 autoreleased 对象。
  1   static inline id autorelease(id obj)
  2   {
  3    assert(obj);
  4    assert(!obj->isTaggedPointer());
  5    id *dest __unused = autoreleaseFast(obj);
  6    assert(!dest  ||  *dest == obj);
  7    return obj;
  8   }
 
  pop 操作
 
  同理,前面提到的 objc_autoreleasePoolPop(void *) 函数本质上也是调用的 AutoreleasePoolPage 的 pop 函数。
  1  void
  2  objc_autoreleasePoolPop(void *ctxt)
  3  {
  4      if (UseGC) return;
  5       // fixme rdar://9167170
  6      if (!ctxt) return;
  7    AutoreleasePoolPage::pop(ctxt);
  8     }
 
  pop 函数的入参就是 push 函数的返回值,也就是 POOL_SENTINEL 的内存地址,即 pool token 。当执行 pop 操作时,内存地址在 pool token 之后的所有 autoreleased 对象都会被 release 。直到 pool token 所在 page 的 next 指向 pool token 为止。
 
  下面是某个线程的 autoreleasepool 堆栈的内存结构图,在这个 autoreleasepool 堆栈中总共有两个 POOL_SENTINEL ,即有两个 autoreleasepool 。该堆栈由三个 AutoreleasePoolPage 结点组成,第一个 AutoreleasePoolPage 结点为 coldPage() ,最后一个 AutoreleasePoolPage 结点为 hotPage() 。其中,前两个结点已经满了,最后一个结点中保存了最新添加的 autoreleased 对象 objr3 的内存地址。
 

 
此时,如果执行 pop(token1) 操作,那么该 autoreleasepool 堆栈的内存结构将会变成如下图所示:
 

 
NSThread、NSRunLoop 和 NSAutoreleasePool
 
根据苹果官方文档中对 NSRunLoop 的描述,我们可以知道每一个线程,包括主线程,都会拥有一个专属的 NSRunLoop 对象,并且会在有需要的时候自动创建。
 
Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed.
 
同样的,根据苹果官方文档中对 NSAutoreleasePool 的描述,我们可知,在主线程的 NSRunLoop 对象(在系统级别的其他线程中应该也是如此,比如通过 dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) 获取到的线程)的每个 event loop 开始前,系统会自动创建一个 autoreleasepool ,并在 event loop 结束时 drain 。我们上面提到的场景 1 中创建的 autoreleased 对象就是被系统添加到了这个自动创建的 autoreleasepool 中,并在这个 autoreleasepool 被 drain 时得到释放。
 
The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.
 
另外,NSAutoreleasePool 中还提到,每一个线程都会维护自己的 autoreleasepool 堆栈。换句话说 autoreleasepool 是与线程紧密相关的,每一个 autoreleasepool 只对应一个线程。
 
Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects.
 
弄清楚 NSThread、NSRunLoop 和 NSAutoreleasePool 三者之间的关系可以帮助我们从整体上了解 Objective-C 的内存管理机制,清楚系统在背后到底为我们做了些什么,理解整个运行机制等。
 
总结
 
看到这里,相信你应该对 Objective-C 的内存管理机制有了更进一步的认识。通常情况下,我们是不需要手动添加 autoreleasepool 的,使用线程自动维护的 autoreleasepool 就好了。根据苹果官方文档中对 Using Autorelease Pool Blocks 的描述,我们知道在下面三种情况下是需要我们手动添加 autoreleasepool 的:
 
如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
 
如果你编写的循环中创建了大量的临时对象;
 
如果你创建了一个辅助线程。
 
最后,希望本文能对你有所帮助,have fun !
 

用户评论

(已有0条评论)
表情
注:您的评论需要经过审核才能显示哦,请文明发言!
还没有评论,快来抢沙发吧!
快速检索
0-9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z