iOS中autorelease的那些事儿

前言

在MRC下, 我们需要手动管理内存, 写一大堆的retain, release代码, 稍不留神就会造成内存泄露; 而ARC下, 编译器帮我们屏蔽掉了这些繁琐的代码, 我们不需要再一条一条地写retain, release了, 可以专心地把精力放在业务逻辑, 技术上.
在MRC下, 调用[object autorelease]可以延迟对象的内存释放; 在ARC下, 我们甚至可以不需要知道 autorelease 是什么都能管理好内存. 编译器帮我们做了什么事情? 到底 autorelease 有什么神奇的地方? autorelease pool 又是个什么东西?下面我将会一一道来.

NSAutoreleasePool 与 @autoreleasepool

NSAutoreleasePool 是 Cocoa 用来支持引用计数内存管理机制的类, 当一个autorelease pool(自动释放池)被drain(销毁)的时候会对pool里的对象发送一条release的消息.
注意 : 在ARC下, 不能使用NSAutoreleasePool这个类来创建自动释放池, 而应该用@autoreleasepool { } 这个block, 官方文档源码如下 :

用以下代码代替上述代码 :

让我们用一张图片来了解对象调用autorelease的整个过程

Object autorelease

补充几点 :
1. 一个对象可以反复调用autorelease方法进入到同一个池中, 相应地, 当池子被销毁时, 该对象将会调用相同次数的release方法, 并不会造成内存泄露. 但并不建议这么做.
2. 程序中至少存在一个自动释放池, 否则autoreleased对象将不能对应收到release消息而导致内存泄露.

自动释放池

3. NSAutoreleasePool对象不能retain, 不能autorelease, 所以drain方法(或者release方法, 但是这两者有所不同, 下文会说)可以直接释放内存. 你应该在同一个上下文(调用创建这个池的同一个方法, 函数或者循环体)中drain一个自动释放池.
4. MRC下需要对象调用autorelease才会入池, ARC下则不需要
5. 不一定要自己创建自动释放池, 但是有3种情况下是很必要的, 下面会讲.
6. 自动释放池可以嵌套使用

autorelease pool 与 线程

每一个线程(包括主线程)都有一个NSAutoreleasePool栈. 当一个新的池子被创建的时候, push进栈. 当池子被释放内存时, pop出栈. 对象调用autorelease方法进入栈顶的池子中. 当线程结束的时候, 它会自动地销毁掉所有跟它有关联的池子.

线程中的自动释放池栈

如果你的应用或者线程是长期存在的并且有可能产生大量的autoreleased对象, 你应该定期地drain和create自动释放池, 否则, autorelease对象会在内存中堆积造成内存告急. 这里借用土土哥的一张图,

循环体中是否使用autoreleasepool的区别

如上图所示, 在每一次循环中, 先创建一个自动释放池, 然后循环结束的时候, 自动释放池销毁, release掉池中对象, 释放内存. 对比之下, 优劣不言而喻.
苹果也是这么做的, 数组的block遍历方法就是, 大家可以自行测试.

数组的block遍历方法

autorelease pool 与 RunLoop

autorelease pool 与 RunLoop

程序运行 -> 开启事件循环 -> 发生触摸事件 -> 创建自动释放池 -> 处理触摸事件 -> 事件对象加入自动释放池 -> 一次事件循环结束, 销毁自动释放池.

苹果官方文档说 :
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

在开始每一个事件循环之前系统会在主线程创建一个自动释放池, 并且在事件循环结束的时候把前面创建的释放池释放, 回收内存. 这里不深入讲解RunLoop, 文章后面会给出几篇RunLoop的文章, 大家可以去看看.

如何管理自动释放池

这里介绍4个方法

  1. release
  2. drain
  3. autorelease
  4. retain

release 和 drain

这里把他们放在一块讲, 是因为他们在引用计数环境下都能销毁一个自动释放池, 为什么这里要特意说明引用计数环境, 因为在引用计数环境和垃圾回收(GC)环境下, 这两个方法不尽相同
引用计数环境下, release 和 drain 效果相同, 均能销毁一个自动释放池.
垃圾回收环境下, drain同上, release 则是一个空方法.
所以建议为了兼容性, 统一用drain吧.

autorelease 和 retain

抛出异常, 因为NSAutoreleasePool不能调用以上 autorelease 和 retain 方法.

怎么使用autorelease pool

由于@autoreleasepool同时兼容MRC和ARC编译环境(NSAutoreleasePool只能在MRC下使用), 所以以下均是以autorelease pool block来介绍使用.
Cocoa 希望代码总是在autorelease pool block中被执行, 否则autoreleased对象就得不到释放从而造成内存泄露.

什么时候需要自己手动创建autorelease pool

看苹果官方文档怎么说明 :

  1. If you are writing a program that is not based on a UI framework, such as a command-line tool.
  2. If you write a loop that creates many temporary objects.
    You may use an autorelease pool block inside the loop to dispose of those objects before the next iteration. Using an autorelease pool block in the loop helps to reduce the maximum memory footprint of the application.
  3. If you spawn a secondary thread.
    You must create your own autorelease pool block as soon as the thread begins executing; otherwise, your application will leak objects.
  1. 你写的程序不是基于UI framework, 例如命令行项目
  2. 你写的循环创建了大量临时对象 -> 你需要在循环体内创建一个autorelease pool block并且在每次循环结束之前处理那些autoreleased对象. 在循环中使用autorelease pool block可以降低内存峰值
  3. 你创建了一个新线程
    当线程开始执行的时候你必须立马创建一个autorelease pool block, 否则你的应用会造成内存泄露.

使用场景 :

  1. 利用@autoreleasepool优化循环, 如上述提过的例子所示
  2. 如果你的应用程序或者线程是要长期运行的并且有可能产生大量autoreleased对象, 你应该使用autorelease pool blocks
  3. 长期在后台中运行的任务, 方法

使用方法 : 不要太简单~~

这里介绍一种特殊的情况

在block结束之后, 你要注意的是任何autoreleased对象已经被处理过了(release). 请不要对这个对象发送消息或者把这个对象当做方法的返回值返回. 会引发野指针错误.
解决方法 : 苹果是这么做的 : 在block内对match对象发送retain消息和在block外对match发送autorelease消息能延长match对象的生命周期并且允许match对象在block外部接收消息或者作为方法的返回值返回. 我们不需要再关心match什么时候释放, 因为它已经交给了上一层的autorelease pool去管理.

欢迎大家关注@Jerry4me, 同时本文有错漏的点恳请大家不吝指出, 谢谢~

参考文档 :
NSAutoreleasePool Class Reference
Using Autorelease Pool Blocks
黑幕背后的Autorelease
@autoreleasepool-内存的分配与释放

这里推荐两个RunLoop的文章
深入理解RunLoop
RunLoop

打赏支持我写出更多好文章,谢谢!

打赏作者

打赏支持我写出更多好文章,谢谢!

任选一种支付方式

1 1 收藏 评论

关于作者:Jerry4me

广工大-大三生 iOS Dev 个人主页 · 我的文章 · 9 ·     

可能感兴趣的话题



直接登录
跳到底部
返回顶部