前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入理解Autorelease Pool

深入理解Autorelease Pool

原创
作者头像
serena
发布2017-12-01 09:58:10
2.5K0
发布2017-12-01 09:58:10
举报
文章被收录于专栏:社区的朋友们社区的朋友们

作者: 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对象的释放时机

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

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • 举个小栗子
    • Autorelease Pool是什么
    • 再举一个小栗子
    • showPools
    • 线程局部存储
    • 小结
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档