为什么已经有了 ARC ,但还是需要 @autoreleasePool 的存在?
避免内存峰值,及时释放不需要的内存空间
面试题
一个很经典的面试题
1 | for (int i = 0; i < MAXFLOAT; i++) { |
上述的这种写法,会使内存慢慢增加,如何解决呢?自动释放池
1 | for (int i = 0; i < MAXFLOAT; i++) { |
AutoreleasePool爆栈的问题
autoreleasePool 的自动 drain是靠runLoop进入空闲来触发的。那么如果你在执行一个很重的任务(如一个很多次数的循环)导致runLoop一直不能进入空闲。 那么autoreleasePool 里面对象会越来越多。导致内存爆掉。因此建议如果是循环次数很大的循环,要么循环里面不要产生autorelease的对象,要么就要手动 添加 @autorelease {}来避免内存爆掉。
1 | //使用容器的block版本的枚举容器时,内部会自动添加一个AutoreleasePool |
什么时间会创建自动释放池?
从程序启动到加载完成是一个完整的运行循环,然后会停下来,等待用户交互,用户的每一次交互都会启动一次运行循环,来处理用户所有的点击事件、触摸事件,运行循环检测到事件并启动后,就会创建自动释放池。 子线程的 runloop 默认是不工作,无法主动创建,必须手动创建。 自定义的 NSOperation 和 NSThread 需要手动创建自动释放池。比如:自定义的 NSOperation 类中的 main 方法里就必须添加自动释放池。否则出了作用域后,自动释放对象会因为没有自动释放池去处理它,而造成内存泄露。
但对于 blockOperation 和 invocationOperation 这种默认的Operation ,系统已经帮我们封装好了,不需要手动创建自动释放池。
@autoreleasepool 当自动释放池被销毁或者耗尽时,会向自动释放池中的所有对象发送 release 消息,释放自动释放池中的所有对象。
如果在一个vc的viewDidLoad中创建一个 Autorelease对象,那么该对象会在 viewDidAppear 方法执行前就被销毁了。
访问 __weak 修饰的变量,是否已经被注册在了 @autoreleasePool 中?为什么?
肯定的,__weak修饰的变量属于弱引用,如果没有被注册到 @autoreleasePool 中,创建之后也就会随之销毁,为了延长它的生命周期,必须注册到 @autoreleasePool 中,以延缓释放。
函数返回一个对象时,会对对象 autorelease 么?为什么?
会 ,为了延长返回对象的生命周期,给其他使用者留足调用的时间
简要说一下 @autoreleasePool 的数据结构?
一个双向链表组成的栈
- AutoreleasePoolPage 组成了一个双向链表,每个page里面都有一个一页的大小的数组,如iOS上一页 0x4000。 每个AutoreleasePoolPage都有个next指针指向下一个可以存储的位置。当next是结尾的时候,这个page就full了。再压入对象就要创建新的page了。
- 一般情况下链表最尾端的一个page是 hotPage. 一般autorelease时对象指针会压入这个page。
- objc_autoreleasePoolPush 会返回当前page的 next的地址作为 token. 然后objc_autoreleasePoolPop 会一直倾倒到这个token的地址。
- push和pop一般出现的地方就是: ① @autorelease {} 所包围的代码的前后; ② runLoop 进入和退出时;③ runLoop进入kCFRunLoopBeforeWaiting状态时,也就是常常说的idle时,会先pop, 再 push.
@autoreleasrPool 的释放时机?
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 了。
Autorelease对象是在当前runloop迭代结束时释放
Autorelease原理
AutoreleasePoolPage
ARC下使用@autoreleasepool{}
来使用一个AutoreleasePool,随后编译器会将其改写成下面的样子
1 | void *context = objc_autoreleasePoolPush(); |
AutoreleasePoolPage结构—-C++实现的类
1 | magic_t const magic; |
- AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小)
- 一个AutoreleasePoolPage空间被占满时,会新建一个AutoreleasePoolPage对象,连接链表,后来的autorelease对象在新的page加入
objc_autoreleasePoolPush
- 当前线程只有一个AutoreleasePoolPage对象时的内存结构 上图中,这一页再加入一个autorelease对象就要满了,也就是next指针马上指向栈顶,这时就要建立下一页page对象,与这一页链表连接完成后,新page的next指针被初始化在栈底(begin的位置),然后继续向栈里添加新对象。
向一个对象发送--autorelease消息,就是将这个对象加入到当前AutoreleasePoolPage的栈顶next指针指向的位置
objc_autoreleasePoolPop
每当进行一次objc_autoreleasePoolPush调用时,runtime会向当前的AutoreleasePoolPage中add进一个哨兵对象,值为0(也就是nil),那么page就变成下面的样子
objc_autoreleasePoolPush的返回值就是这个哨兵对象的地址,objc_autoreleasePoolPop(哨兵对象)作为入参
- 释放过程
- 根据传入的哨兵对象地址找到哨兵对象所处的page
- 在当前page中,将晚于哨兵对象(可根据parent跨越page)插入的所有autorelease对象都发送一次release消息,并向回移动next指针到正确位置
上面Pop之后的内存状态如下
嵌套的AutoreleasePool
pop的时候总会释放到上次push的位置为止,多层的pool就是多个哨兵对象而已