深入理解Autorelease Pool

作者: rhythmhuang

前言

MRC下,我们需要手动管理对象的retain和release,或者调用autorelease方法把对象放进AutoreleasePool中来进行内存管理。在ARC下,我们甚至都不用知道retain,release,autorelease这些方法就能管理好内存。尽管这样,深入了解AutoreleasePool对于我们解决一些棘手的问题还是很有必要,甚至是必须的。

比如上面的crash堆栈,在了解了autorelease的原理之后,就能够大概知道crash产生的原因以及解决方法。

举个小栗子

这里的输出是这样的:

这个例子说明了,当一个object加入了Autorelease Pool之后,它就不是在这个对象所在方法结束的时候release了。其实它的release会延迟到当前runloop执行完之后,为什么会这样呢?

Autorelease Pool是什么

我们平常使用到的Autorelease Pool的情景,大部分是这样写的:

在编译时,编译器会自动把它编译为如下形式:

在执行@autoreleasepool方括号中的代码前,会有一个push操作。在执行完@autoreleasepool方括号中的代码后,还会有一个pop的操作与之对应。通过分析NSObject源码,我们可以看到Autorelease Pool的真面目:

Autorelease Pool的本质上是一个双向链表。双向链表中每一页为一个AutoreleasePoolPage,AutoreleasePoolPage最大为4096B,每当AutoreleasePoolPage中因为存储变量总大小超过4096B之后,就会分配一个新的AutoreleasePoolPage:

AutoreleasePoolPage中的parent指针指向上一页,child指针指向下一页。next指针指向当前页中下一个可以存储变量的地址。AutoreleasePoolPage除了这些属性之外,开辟了一段连续的区域来记录放入Autorelease Pool的变量的地址。

对于像这样嵌套的@autoreleasepool代码,Autorelease Pool的双向链表是怎么使用的呢?

Autorelease Pool当遇到嵌套的@autoreleasepool时,会先插入一个“0”,称之为哨兵对象(POOL_SENTINEL)。嵌套内的@autoreleasepool中加入Autorelease Pool中的变量,会在哨兵对象之后插入AutoreleasePoolPage。当执行完嵌套内的@autoreleasepool之后,AutoreleasePool会调用objc_autoreleasePoolPop()方法把哨兵对象之前的变量全部出栈,恢复到进入嵌套内@autoreleasepool之前的状态。

再举一个小栗子

如果把objectWithNumber:方法中的__autoreleasing去掉,那么输出会有怎样的变化呢?

此时,生成的TextObject对象在viewDidLoad执行完之后,就马上被释放掉了,在viewWillAppear执行的时候,TextObject对象已经为null了。这是为什么呢?我们可以使用[NSAutoreleasePool showPools]方法来检查在这种情况下,Autorelease Pool的情况。

showPools

使用[NSAutoreleasePoolshowPools]方法可以在调试的时候方便的随时查看当前AutoreleasePool的变量存储情况。我们在需要解决AutoreleasePool相关的问题时可以使用它方便地定位问题。

有__autoreleasing时:

没有__autoreleasing时:

在使用__autoreleasing修饰符修饰初始化出来的变量时,变量会加入到Autorelease Pool中。

在没有使用__autoreleaseing修饰符修饰初始化出来的变量时,变量并没有加入到Autorelease Pool中。

线程局部存储

在Xcode中查看没有__autoreleasing修饰符时,初始化TestObject的方法,可以看到如下代码:

可以看到,初始化出来的TestObject并没有加入Autorelease Pool,而是调用了_objc_autoreleaseReturnValue()。

TLS(线程局部存储)则是一块属于某一线程的专有存储,使用Key-Value的形式读写。能以比autorelease更快的速度进行对象的存储和读取。

编译器在编译阶段会通过上下文判断初始化出来的对象。如果调用方与被调用方法都是ARC的,编译器会做一个流程上的优化,在被调用方使用_objc_autoreleaseReturnValue()把对象放到TLS上。而在调用方使用_objc_retainAutoreleasedReturnValue()读取TLS上的对应对象。使用TLS做中转,避免了把对象放进Autorelease Pool的retain和release操作。这相当于是编译器的一项优化。

小结

本文主要讲述了以下内容:

1.Autorelease Pool是以双向链表形式存储的。

2.在ARC模式下,编译器会使用TLS优化变量的存储。

参考资料

1.黑幕背后的Autorelease

2.ARC环境下编译器到底对autorelease对象做了怎样的优化

3.自动释放池的前世今生---- 深入解析 autoreleasepool

4.探索子线程autorelease对象的释放时机

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏IT派

Python高级特性

以上几个特性我会针对应用场景,使用注意事项,应用举例几个维度分别进行讲解,如果有同学对某个特性特别熟悉则可以直接跳过。

922
来自专栏Jimoer

Java设计模式学习记录-模板方法模式

模板方法模式,定义一个操作中算法的骨架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

1054
来自专栏Python

Django——model基础

ORM 映射关系:     表名 <-------> 类名 字段 <-------> 属性     表记录 <------->类实例对象...

16910
来自专栏JAVA高级架构开发

Java虚拟机学习:方法调用的字节码指令

我们在写java程序的时候会进行各种方法调用,虚拟机在执行这些调用的时候会用到不同的字节码指令,共有如下五种: 

450
来自专栏SDNLAB

Ryu:OpenFlow协议源码分析

Ryu支持OpenFlow所有的版本,是所有SDN控制器中对OpenFlow支持最好的控制器之一。这得益于Ryu的代码设计,Ryu中关于OpenFlow协议的代...

38411
来自专栏丑胖侠

《Drools7.0.0.Final规则引擎教程》第4章 4.5RHS语法

RHS语法 使用说明 RHS是满足LHS条件之后进行后续处理部分的统称,该部分包含要执行的操作的列表信息。RHS主要用于处理结果,因此不建议在此部分再进行业务判...

2198
来自专栏魂祭心

原 Data Access Compone

3376
来自专栏冰霜之地

深入研究Block捕获外部变量和__block实现原理

Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这个新功能“Blocks”。从那开始,Block就出现...

743
来自专栏欧阳大哥的轮子

手把手教你查看和分析iOS的crash崩溃异常

一个应用程序并不总会一直运行的很好,它总会有出现crash崩溃的情况。如果在应用程序中接入了一些第三方的crash收集工具或者自建crash收集报告平台的话将会...

1392
来自专栏SpringBoot 核心技术

第三十六章:基于SpringBoot架构重写SpringMVC请求参数装载

2317

扫码关注云+社区