iOS AutoreleasePool 实现原理

  最近在梳理基础知识,前两天根据自己的理解,以及参考同行大家的文章,整理了一下autorelease 的实现原理,自己的东西没有,大家别见笑,具体内容如下:

什么是AutoreleasePool

  AutoreleasePool(自动释放池)是OC中的一种内存自动回收机制,它可以延迟加入AutoreleasePool中的变量release的时机。在正常情况下,创建的变量会在超出其作用域的时候release,但是如果将变量加入AutoreleasePool,那么release将延迟执行。

什么样的场景下需要显式用到AutoreleasePool

  分别有三种情况:

  • autorelease 机制基于 UI framework。因此写非UI framework的程序时,需要自己管理对象生存周期。
  • autorelease 触发时机发生在下一次runloop的时候。因此如何在一个大的循环里不断创建autorelease对象,那么这些对象在下一次runloop回来之前将没有机会被释放,可能会耗尽内存。这种情况下,可以在循环内部显式使用@autoreleasepool {}将autorelease 对象释放。这种情况我们会比较经常遇到,具体代码如下:
for (item in BigSet){

    @autoreleasepool {
        //create large mem objects
    }
}

还有就是enumerateObjectsUsingBlockNSDictionary,NSArray通过这个枚举方法遍历的时候,其实内部已经实现了autorelease操作。

  • 自己创建的线程。Cocoa的应用都会维护自己autoreleasepool。因此,代码里spawn的线程,需要显式添加autoreleasepool。注意:如果是使用POSIX API 创建线程,而不是NSThread,那么不能使用Cocoa,因为Cocoa只能在多线程(multithreading)状态下工作。但可以使用NSThread创建一个马上销毁的线程,使得Cocoa进入multithreading状态。
    参考:
    https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmAutoreleasePools.html

https://blog.csdn.net/zcmuczx/article/details/80034923

ARC下为什么还要显式的使用autoreleasepool

  ARC中release/retain等方法不能使用,但ARC 会帮你在合适的地方插入这些方法。这将导致内存的延迟释放。autoreleasepool是为了 autorelease 这个方法,在对象的创建者没法销毁对象的时候,可以使用autorelease让autoreleasepool每隔一段时间检查该对象的引用计数,如果为0则释放对象。那么多个autoreleasepool的作用就是增加这种间隔,比原本autorelease释放的时间更早释放。

autoreleasepool的创建和释放

  • App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
  • 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
  • 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
  • 在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。

也就是说AutoreleasePool创建是在一个RunLoop事件开始之前(push),AutoreleasePool释放是在一个RunLoop事件即将结束之前(pop)。
AutoreleasePool里的Autorelease对象的加入是在RunLoop事件中,AutoreleasePool里的Autorelease对象的释放是在AutoreleasePool释放时。
具体如下图:

转载地址:https://www.jianshu.com/p/50bdd8438857

autoreleasepool的实现原理

ARC下,我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器将其改写成下面的样子:


void *context = objc_autoreleasePoolPush();// {}中的代码objc_autoreleasePoolPop(context);

而这两个函数都是对AutoreleasePoolPage的简单封装,所以自动释放机制的核心就在于这个类。

AutoreleasePoolPage是一个C++实现的类
iOS AutoreleasePool 实现原理_第1张图片

AutoreleasePoolPage

AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)。

AutoreleasePool是按线程一一对应的(结构中的thread指针指向当前线程)。

AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址。

上面的id *next指针作为游标指向栈顶最新add进来的autorelease对象的下一个位置。

一个AutoreleasePoolPage的空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入。

所以,若当前线程中只有一个AutoreleasePoolPage对象,并记录了很多autorelease对象地址时,内存如下图:
iOS AutoreleasePool 实现原理_第2张图片
内存情况

上图中的情况,这一页再加入一个autorelease对象就要满了(也就是next指针马上指向栈顶),这时就要执行上面说的操作,建立下一页page对象,与这一页链表连接完成后,新page的next指针被初始化在栈底(begin的位置),然后继续向栈顶添加新对象。

所以,向一个对象发送- autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置。

释放时刻

每当进行一次objc_autoreleasePoolPush调用时,runtime向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是个nil),那么这一个page就变成了下面的样子:

效果图
iOS AutoreleasePool 实现原理_第3张图片

objc_autoreleasePoolPush的返回值正是这个哨兵对象的地址,被objc_autoreleasePoolPop(哨兵对象)作为入参,于是:

1、根据传入的哨兵对象地址找到哨兵对象所处的page。

2、在当前page中,将晚于哨兵对象插入的所有autorelease对象都发送一次- release消息,并向回移动next指针到正确位置。

3、补充2:从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page,刚才的objc_autoreleasePoolPop执行后,最终变成下面的样子:

执行效果图
iOS AutoreleasePool 实现原理_第4张图片

嵌套的AutoreleasePool

知道了上面的原理,嵌套的AutoreleasePool就非常简单了,pop的时候总会释放到上次push的位置为止,多层的pool就是多个哨兵对象而已,就像剥洋葱一样,每次一层,互不影响。
转载地址:https://blog.csdn.net/linfengwenyou/article/details/47054205
另外通过梳理知识,也产生了一些疑问:OC中变量延迟释放 的情形有哪些 后续会进一步的梳理。关于双链表的各种操作也要练习一下。
更多优质文章,可以微信扫码关注:
这里写图片描述

你可能感兴趣的:(Object-C,IOS)