设为首页 加入收藏

TOP

Objective-C Autorelease Pool 的实现原理(二)
2015-07-16 12:04:17 来源: 作者: 【 】 浏览:208
Tags:Objective-C Autorelease Pool 实现 原理
atchpoint 命令来设置观察点,观察全局变量 string_weak_ 的值的变化,string_weak_ 变量保存的就是我们创建的 autoreleased 对象的地址。在这里,我们再次利用了 __weak 变量的另外一个特性,就是当它所指向的对象被释放时,__weak 变量的值会被置为 nil 。了解了基本原理后,我们开始验证上面的猜测。

我们先在第 35 行打一个断点,当程序运行到这个断点时,我们通过 lldb 命令 watchpoint set v string_weak_ 设置观察点,观察 string_weak_ 变量的值的变化。如下图所示,我们将在 console 中看到类似的输出,说明我们已经成功地设置了一个观察点:

设置观察点

设置好观察点后,点击 Continue program execution 按钮,继续运行程序,我们将看到如下图所示的界面:

设置观察点

我们先看 console 中的输出,注意到 string_weak_ 变量的值由 0x00007f9b886567d0 变成了 0x0000000000000000 ,也就是 nil 。说明此时它所指向的对象被释放了。另外,我们也可以注意到一个细节,那就是 console 中打印了两次对象的值,说明此时 viewWillAppear 也已经被调用了,而 viewDidAppear 还没有被调用。

接着,我们来看看左侧的线程堆栈。我们看到了一个非常敏感的方法调用 -[NSAutoreleasePool release] ,这个方法最终通过调用 AutoreleasePoolPage::pop(void *) 函数来负责对 autoreleasepool 中的 autoreleased 对象执行 release 操作。结合前面的分析,我们知道在 viewDidLoad 中创建的 autoreleased 对象在方法返回后引用计数为 1 ,所以经过这里的 release 操作后,这个对象的引用计数 -1 ,变成了 0 ,该 autoreleased 对象最终被释放,猜测得证。

另外,值得一提的是,我们在代码中并没有手动添加 autoreleasepool ,那这个 autoreleasepool 究竟是哪里来的呢?看完后面的章节你就明白了。

场景 2:同理,当通过 [NSString stringWithFormat:@leichunfeng] 创建一个对象时,这个对象的引用计数为 1 。而当使用局部变量 string 指向这个对象时,这个对象的引用计数 +1 ,变成了 2 。而出了当前作用域时,局部变量 string 变成了 nil ,所以其所指向对象的引用计数变成 1 。另外,我们知道当出了 @autoreleasepool {} 的作用域时,当前 autoreleasepool 被 drain ,其中的 autoreleased 对象被 release 。所以这个对象的引用计数变成了 0 ,对象最终被释放。

场景 3:同理,当出了 @autoreleasepool {} 的作用域时,其中的 autoreleased 对象被 release ,对象的引用计数变成 1 。当出了局部变量 string 的作用域,即 viewDidLoad 方法返回时,string 指向了 nil ,其所指向对象的引用计数变成 0 ,对象最终被释放。

理解在这 3 种场景下,autoreleased 对象什么时候释放对我们理解 Objective-C 的内存管理机制非常有帮助。其中,场景 1 出现得最多,就是不需要我们手动添加 @autoreleasepool {} 的情况,直接使用系统维护的 autoreleasepool ;场景 2 就是需要我们手动添加 @autoreleasepool {} 的情况,手动干预 autoreleased 对象的释放时机;场景 3 是为了区别场景 2 而引入的,在这种场景下并不能达到出了 @autoreleasepool {} 的作用域时 autoreleased 对象被释放的目的。

PS:请读者参考场景 1 的分析过程,使用 lldb 命令 watchpoint 自行验证下在场景 2 和场景 3 下 autoreleased 对象的释放时机,you should give it a try yourself 。

AutoreleasePoolPage

细心的读者应该已经有所察觉,我们在上面已经提到了 -[NSAutoreleasePool release] 方法最终是通过调用 AutoreleasePoolPage::pop(void *) 函数来负责对 autoreleasepool 中的 autoreleased 对象执行 release 操作的。

那这里的 AutoreleasePoolPage 是什么东西呢?其实,autoreleasepool 是没有单独的内存结构的,它是通过以 AutoreleasePoolPage 为结点的双向链表来实现的。我们打开 runtime 的源码工程,在 NSObject.mm 文件的第 438-932 行可以找到 autoreleasepool 的实现源码。通过阅读源码,我们可以知道:

  • 每一个线程的 autoreleasepool 其实就是一个指针的堆栈;
  • 每一个指针代表一个需要 release 的对象或者 POOL_SENTINEL(哨兵对象,代表一个 autoreleasepool 的边界);
  • 一个 pool token 就是这个 pool 所对应的 POOL_SENTINEL 的内存地址。当这个 pool 被 pop 的时候,所有内存地址在 pool token 之后的对象都会被 release ;
  • 这个堆栈被划分成了一个以 page 为结点的双向链表。pages 会在必要的时候动态地增加或删除;
  • Thread-local storage(线程局部存储)指向 hot page ,即最新添加的 autoreleased 对象所在的那个 page 。

    一个空的 AutoreleasePoolPage 的内存结构如下图所示:

    AutoreleasePoolPage

    1. magic 用来校验 AutoreleasePoolPage 的结构是否完整;
    2. next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin()
    3. thread 指向当前线程;
    4. parent 指向父结点,第一个结点的 parent 值为 nil
    5. child 指向子结点,最后一个结点的 child 值为 nil
    6. depth 代表深度,从 0 开始,往后递增 1;
    7. hiwat 代表 high water mark 。

      另外,当 next == begin() 时,表示 AutoreleasePoolPage 为空;当 next == end() 时,表示 AutoreleasePoolPage 已满。

      Autorelease Pool Blocks

      我们使用 clang -rewrite-objc 命令将下面的 Objective-C 代码重写成 C++ 代码:

      1
      2
      3
      
      @autoreleasepool {
      
      }
      

      将会得到以下输出结果(只保留了相关代码):

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
      extern C __declspec(dllimport) void * objc_autoreleasePoolPush(void);
      extern C __declspec(dllimport) void objc_autoreleasePoolPop(void *);
      
      struct __AtAutoreleasePool {
        __AtAutoreleasePool() {atautorel
      首页 上一页 1 2 3 4 下一页 尾页 2/4/4
      】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
      分享到: 
      上一篇OC教程6-代码块block回调 下一篇OC教程5-委托delegate模式回调

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: