iOS autorelease与自动释放池

autorelease、autorelease pool以及原理

autorelease与MRC、ARC

  • autorelease:在MRC下,内存管理允许有三个操作,分别是release,retain,autoreleaserelease会使对象的引用计数立刻-1,retain使对象的引用计数立刻+1,autorelease也会让对象的引用计数-1,但不是立刻-1.调用autorelease的对象会被加入到autorelease pool中,在合适的时间autorelease pool向对象调用release,也就是说,对象被延迟释放了。
  • 而在ARC下,Apple禁止了手动调用autorelease方法。使用@autoreleaseblock创建自动释放池后,runtime会自动向在block中的对象加上autorelease

autorelease pool

A thread's autorelease pool is a stack of pointers. Each pointer is either an object to release, or POOL_BOUNDARY which is an autorelease pool boundary. A pool token is a pointer to the POOL_BOUNDARY for that pool. When the pool is popped, every object hotter than the sentinel is released. The stack is divided into a doubly-linked list of pages. Pages are added and deleted as necessary. Thread-local storage points to the hot page, where newly autoreleased objects are stored.

以上是objc-781源码中NSObject.mm对于自动释放池的定义。从定义里面可以得知,自动释放池实际上是一个存放了指针的栈,栈中的指针有两类,一类是等待释放的对象指针,一类是名为POOL_BOUNDARY的哨兵指针。释放池之间以链表的形式相连,一个Page通常是4096个字节的大小(虚拟内存中的一页)。而前面提到的POOL_BOUNDARY哨兵指针的作用就是标示每个池子的尾端。

当在MRC中调用autorelease方法或者在ARC中将对象编写在@autoreleaseblock中,对象将会被注册到自动释放池中,当合适的时机到来自动释放池将会向这些对象调用release方法,以释放对象。

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. If you use the Application Kit, you therefore typically don’t have to create your own pools. If your application creates a lot of temporary autoreleased objects within the event loop, however, it may be beneficial to create “local” autorelease pools to help to minimize the peak memory footprint.

以上是Developer Documentation中Apple对于autorelease pool的一个介绍。可以看到在主线程中,每一个事件循环Runloop的开始,Appkit框架都会为程序创建一个自动释放池,并且在每次Runloop结束时释放所有在池中的对象。
需要注意的是,在这里Apple提到了一点:如果程序中临时创建了大量的autorelease对象,那么更好的做法是开发者自行新增一个释放池来最小化内存峰值的发生。

原理

First of all,最简单的代码

我们先来写出最常见的@autorelease代码。

-w461

当我们在Xcode中建立一个macOS项目时,通常模版中的main函数就包含了这样一段类似的代码。其中的@autorelease pool就是在ARC环境下使用自动释放池的API。

OC to C++

在终端中使用clang -rewrite-objc main.m命令将main.m文件转换成C++代码文件。
转换出来的代码会很多,我们挑重点的看。

-w511

在C++代码的main函数中,@autoreleasepool{}已经被转换成了如上代码。我们可以看到熟悉的objc_msgSeng,这是OC的灵魂-消息发送。
同时,@autoreleasepool{}变成了__AtAutoreleasePool,看来自动释放池的真实结构就是这个。我们再找一下它的定义在哪里。

通过搜索关键字,我们找到了它的定义语句。


-w543

可以看到,__AtAutoreleasePool结构体中定义了一个构造函数和一个析构函数,并调用了objc_autoreleasePoolPush()objc_autoreleasePoolPop()两个函数。

objc源码

这一步,我们到objc4-781代码中找上述两个方法的实现。

-w397

可以看到,这两个方法实际上是调用了AutoreleasePoolPage类的push()pop()方法,也就是说,自动释放池在runtime中的实际结构其实是AutoreleasePoolPage,这就是它的最终面目了。

-w596

AutoreleasePoolPage类继承于AutoreleasePoolPageData

-w766

从这里我们可以看到,autoreleasepool 是与线程一一对应的。同时线程池之间以双向链表相连。

这里引用网上一位同学分享的内存分布图

AutoreleasePoolPage

接着我们来看一下几个关键方法的具体实现。

autorelease

首先是autorelease方法。调用该方法会将对象加入到自动释放池中。

autorelease

第一行和第二行代码分别对传入参数做了一些检验,从第二行代码可以见到,如果传入的对象是TaggedPointer类型的,比如由小于等于9个字符的字面量字符串创建的NSString,将会有其他的处理操作。

autoreleaseFast-w465

该方法是autorelease方法的关键方法。可以看到第一行通过hotPage()方法拿到一个最近使用过的Page,然后来到流程控制。

  • 如果获取到了该hotPage并且Page还没有满,那么将对象加入到该Page中;
  • 如果Page满了,则调用autoreleaseFullPage方法创建一个新的page,将对象加入到新创建的page后并将新建立的page与通过hotPage()获取到的page相连接。
  • 如果没有获取到hotpage,那么将会调用autoreleaseNoPage方法建立并初始化自动释放池。

AutoreleasePoolPage::push()

在前面我们提到将对象加入到自动释放池时首先调用objc_autoreleasePoolPush方法,而该方法只起到了调用AutoreleasePoolPage::push()方法的作用。

-w688

其中,if-else的if分支是当出错时Debug会执行的流程,正常将会执行else分支里的代码。而autoreleaseFast()方法的实现在上一小段中已给出,这里传入的POOL_BOUNDARY就是哨兵对象。当创建一个自动释放池时,会调用该push()函数。

_objc_autoreleasePoolPrint()

使用_objc_autoreleasePoolPrint();方法可以打印出目前自动释放池中的对象,当然在使用前要先extern void _objc_autoreleasePoolPrint(void);.
该方法会调用AutoreleasePoolPage::printAll();打印出自动释放池中的相关信息。

-w553

从打印出的信息来看,自动释放池确实是跟线程一一对应的,并且在创建时会将一个哨兵对象加入到池中,这与我们在上文的代码分析结果相互映证。

写在最后

关于autoreleaseautoreleasePool的原理和代码的分析大概就是这些,当然还有很多具体实现是本文没有提到的,有兴趣的读者也可以自行到objc4-781的源码里找到NSObject.mm文件更加详细地研究。
总得来说,自动释放池机制延迟了对象的生命周期,并且可以为开发者自动释放需要被释放的对象,减少了内存泄漏发生的可能。


Tino Wu.
more at tinowu.top

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,919评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,567评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,316评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,294评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,318评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,245评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,120评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,964评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,376评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,592评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,764评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,460评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,070评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,697评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,846评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,819评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,665评论 2 354

推荐阅读更多精彩内容