ios autoReleasePool的一点总结Xmind

444 阅读5分钟

AutoReleasePool.png

高清图片请私信我

ARC & MRC

相同点:都需要通过添加retain、release、autorelease等控制引用计数的加减

不同点:ARC 是通过LLVM编译器和Runtime在合适的地方添加

release & autorelease

相同点

  • 都会对对象的引用计数实现-1操作
  • 在ARC环境下都被不能显示添加

不同点:

  • release:立即-1,对象引用计数为0时,立即释放(销毁)
  • autoRelease:系统在恰当的时候给对象进行release,延迟释放

创建方式

MRC

  • NSAutoreleasePool
  • @autoreleasepool{}

ARC

  • 只能使用@autoreleasepool{}

原理@autoreleasepool{}

__AtAutoreleasePool:

底层为C++的__AtAutoreleasePool类型的结构体

  • 结构体构造函数

    • 调用objc_autoreleasePoolPush(),返回atautoreleasepoolobj

      • // NSObject.mm
        void * objc_autoreleasePoolPush(void)
        {
        return AutoreleasePoolPage::push();
        }
  • 结构体析构函数

    • 调用objc_autoreleasePoolPop()函数,并将atautoreleasepoolobj传入。

      • // NSObject.mm
        void objc_autoreleasePoolPop(void *ctxt)
        {
        AutoreleasePoolPage::pop(ctxt);
        }

AutoreleasePoolPage:

构造和析构都是通过AutoreleasePoolPage对象实现的push() 和 pop方法实现的

  • ARP是多个page结构,以栈为节点,通过双向链表的形式组合而成

  • 每个page的大小:
    4096字节,成员变量7个,占用56字节,其余4040以栈结构方式存储autorelease对象的地址

  • autorelease对象从高地址向低地址进行存储,并且以POOL_BOUNDARY哨兵对象开头进行存储,其实用来区分不同的ARP,特别是嵌套的时候

    • POOL_BOUNDARY
      哨兵对象或边界对象

      • 用来区分不同的ARP,解决ARP的嵌套问题,而不是Page,可以理解为其实ARP的唯一性标志

      • 每创建一个ARP,就会调用push(),
        将POOL_BOUNDARY入栈,并返回其存放的内存地址,
        这个地址可以理解为是当前栈对应的下一个位置;

        • 当ARP添加autorelease对象时,
          将对象的地址入栈,对象前面至少有一个 POOL_BOUNDARY

        • push方法

          • 创建ARP的时候调用,方法内部调用 autoreleaseFast(POOL_BOUNDARY)

          • autoreleaseFast的实现

            • 调用hotPage()方法获得未满的Page
              在autoreleaseFast知心过程中,
              可能出现的情况

              • Page存在,未满

                • 通过page->add(obj)将autorelease对象入栈

                  • page->add(obj):将autorelease对象添加到Page中的next指针所指向的位置,
                    并将next指针指向这个对象的下一个位置,
                    然后将该对象的位置返回。
              • Page存在,已满

                • autoreleaseFullPage(obj, page)
                  创建一个新的Page,并将autorelease对象入栈

                  • 通过while循环,结合page的child指针直接找到最后一个page

                    • page未满

                    • page已满

                      • 新建一个page,设置为hotPage,然后添加对象
              • Page不存在

                • autoreleaseNoPage(obj)
                  创建第一个Page并将autorelease对象添加进去

                  • 1.判断是否有空的ARP(理论上是没有),
                    如果没有,调用setEmptyPoolPlaceholder()
                    生成一个占位符,表示一个空ARP
                  • 2.创建第一个Page
                  • 3.添加POOL_BOUNDARY,
                    返回POOL_BOUNDARY的下一个位置
      • 当ARP销毁的时候,调用pop()方法,
        并传入一个POOL_BOUNDARY,会从ARP中的最后一个对象开始,依次发送release消息,直到遇到POOL_BOUNDARY

        • pop(token)方法

          • 简单概括:ARP销毁的时候,调用pop()方法,实际是给ARP内的入栈的autorelease对象依次放release消息,直到遇到POOL_BOUNDARY

          • 执行过程

            • 1.判断传入token是不是
              EMPTY_POOL_PLACEHOLDER

              • 是,则清空这个ARP
              • 不是,转到下面第2
            • 2.通过pageForPointer(token)拿到token所在的Page,及ARP的第一个page

            • 3.通过page->releaseUntil(stop),将ARP中的autorelease对象全部释放(发release消息)
              stop:就是POOL_BOUNDARY

              • 通过while循环遍历,
                从最后一个入栈的autorelease对象开始
                依次发送release消息
                直到遇到POOL_BOUNDARY
            • 4.判断page是否有子Page,有就销毁 ??有疑问

          • page->releaseUntil(token)

            • 通过while循环,从最后一个入栈的autorelease对象开始,依次销毁(发送release),直到遇到POOL_BOUNDARY
  • AutoreleasePoolPage创建

    • 1.参数为parentPage,即上一层page的地址,猜想,第一个page的parentPage为nil
    • 2.page的depth +1
    • 3.page的next指针指向bagin()
    • 4.page的parent指针指向参数parentPage
      page的child指针执行自己
      形成闭环双链表
  • begin、end、empty、full

    • begin:Page自己的地址+Page对象的大小56个字节;
    • end: Page自己的地址+4096个字节
    • empty:next指针指向地址 == begin
    • full: next指针指向地址 = end

对象在调用autorelease方法

通过调用autoreleaseFast()添加进page

查看ARP情况

1.声明函数:

extern void _objc_autoreleasePoolPrint(void);

在需要的地方调用此函数

与Runloop的关系

概括

  • 循环开始时,主线程创建一个ARP
  • 循环结束时,销毁ARP,
    并释放ARP中的所有autorelease对象

主线程的RunLoop注册了2个observer

  • 第1个监听 kCFRunLoopEntry事件,
    自动创建__AtAutoreleasePool,
    调用objc_autoreleasePoolPush()

  • 第2个监听

    • kCFRunLoopBeforeWaiting
      即将休眠

      • 销毁1监听中的__AtAutoreleasePool,
        并调用objc_autoreleasePoolPop()
      • 重新创建一个__AtAutoreleasePool,
        并调用objc_autoreleasePoolPush()
    • kCFRunLoopBeforeExit
      即将退出RunLoop

      • 销毁休眠前创建的 __AtAutoreleasePool,
        并调用objc_autoreleasePoolPop()

ARC先autorelease对象什么时候释放

系统干预:

1.RunLoop线程休眠,2.RunLoop线程销毁

人工干预:

手动添加autoreleasePool,在大括号结束后销毁

ARC 环境下,需不需要手动添加

@autoreleasepool

AppKit 和 UIKit 框架会在RunLoop每次事件循环迭代中创建并处理@autoreleasepool,无需自动创建

手动添加@autoreleasepool来管理这些对象可以很大程度地减少内存峰值

苹果建议的手动添加的情况

  • 如果你编写的程序不是基于 UI 框架的,比如说命令行工具;
  • 如果你编写的循环中创建了大量的临时对象;
    你可以在循环内使用@autoreleasepool在下一次迭代之前处理这些对象。
    在循环中使用@autoreleasepool有助于减少应用程序的最大内存占用。
  • 如果你创建了辅助线程。
    一旦线程开始执行,就必须创建自己的@autoreleasepool;否则,你的应用程序将存在内存泄漏。

\