内存管理(六)autorelease
00 分钟
2020-10-18
2020-10-18
type
status
date
slug
summary
tags
category
icon
password
本文分为三部分:
两节主要讲述自动释放池的概念和应用,其中二中还详述了autorelease与方法返回值的关系。其中涉及的示例代码在自动释放池-应用
探索了自动释放池的原理,但是没有对源码进行更多的描述,只是阐述了机制的运作。相关示例代码在自动释放出-原理
本文涉及到的objc源码,来自于objc源码,版本是723。

一、自动释放池

自动释放池,autorelease pool,是MRC就存在的一种机制,这种机制使得"对象延迟释放"成为可能。
notion image
只要将对象放入自动释放池后,系统会在合适的时机,对对象进行释放操作。
不管是MRC,还是ARC下,自动释放池的机制都是相同的,不同的关键字所代表的含义也是一致的。比如autorelease__autorelease@autoreleasepool{},但需要注意的是NSAutoreleasePool和前面几个机制不一样。

1.1 MRC

notion image

1.2 ARC

notion image
ARC下,另外一个常见的使用——降低内存峰值
notion image

二、autorelease与方法返回值

在MRC时代,autorelease另外一个常用的场景就是方法返回值。
notion image
下面,我们声明一个类,并且通过不同的方式返回一个类对象:
按照苹果的命名规则,-initWithName:retained return value,方法内部没有对对象进行释放,调用者拥有对象,需要释放。
-personWithName:就是unretained return value,内部进行了释放,但是是延迟释放,调用者不拥有对象,不需要进行释放。

2.1 retained return value

notion image

2.2 unretained return value

notion image

三、原理

自动释放池的机制,奥秘在于"自动",而"自动"很容易让我们联想到iOS中最常见的"自动"机制——RunLoop。
而释放池,是一个池子,其实是一种数据结构,这种结构需要支持在池子里面的对象,能在恰当的时机发送release消息。
所以,了解自动释放池的原理,就是两个目标:掌握"自动"释放的时机,以及如何构建这个池子

3.1 池子

我们先尝试如下代码:
将其转为C++:
我们得到如下:
刨去复杂的结构转换,关注__AtAutoreleasePool __autoreleasepool;,是的,这个就是"池子"本身。
根据其构造和析构函数,最后转换为:
notion image
objc源码源码中对应的函数为:
其实,AutoreleasePoolPage才是管理autorelease对象的真正结构。
notion image
上图展示了AutoreleasePoolPage对象结构,下面一览其存储结构:
notion image
AutoreleasePoolPage这个"池子"负责了所有的autorelease对象的管理,每个Page除了存放自己的成员变量之外,其余空间用来存放autorelease对象,如果当前Page不够存储,会新建Page,并进行双链表的连接,然后将autorelease对象存储在新Page。而next指针则指向了下一个存放autorelease对象的地址。

3.2 时机

在了解其自动释放池的结构之后,就剩下如何触发"自动释放"这个问题。
在了解这个时机之前,我们先看下上面一节重写为C++代码中pushpop都做了什么。
notion image
pushpop,是对应进行清理工作的两个动作。那么这两个动作何时调用,即时机,就是我们需要探讨的。
我们在RunLoop(四)应用中讲述了RunLoop是如何调度autoreleasepool的。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
启动时,会在Runloop里注册两个Observer。
  • 第一个Observer会监听
  • 即将进入Loop,其回调内会调用objc_autoreleasePoolPush(),向当前的AutoreleasePoolPage增加一个哨兵对象标志创建自动释放池。这个Observer的order是-2147483647优先级最高,确保发生在所有回调操作之前。
  • 第二个Observer会监听
  • 即将进入休眠:会调用objc_autoreleasePoolPop()objc_autoreleasePoolPush() ,根据情况从最新加入的对象一直往前清理直到遇到哨兵对象。
  • 即将退出RunLoop:会调用objc_autoreleasePoolPop() 释放自动自动释放池内对象。这个Observer的order是2147483647,优先级最低,确保发生在所有回调操作之后。
最后,我们绘制如下图:
notion image

参考

链接

  1. 黑幕背后的Autorelease
  1. NSError and __autoreleasing
  1. Transitioning to ARC Release Notes
  1. objc源码

示例代码

  1. 自动释放池-应用
  1. 自动释放出-原理