Objc 自动释放池平时不多显式的使用,但其实它时刻在默默为咱们工做。关于自动释放池源码分析的文章已经不少了,本文不会在源码层面剖析原理。面试
在 MRC 时代,须要使用retain
和release
手动维护对象的引用计数,并要遵循「谁建立谁释放」的原则。数据结构
然而在某些场景下没法知足这个原则,好比说工厂方法:ide
+ (id)factory {
return [self new];
}
复制代码
在return
处若是调用retain
,就须要调用方负责 release
,这显然是不科学的设计。因此这里不能retain
, 可是不 retain
,该对象超出做用域后就会被释放,调用方取到的会是 nil
。该如何保证调用方在这个对象超出做用域后,还能取到呢?方法就是自动释放池,在返回前,将该对象被加入自动释放池,这样调用方就能顺利取到返回值了。oop
那若是对象真的须要被释放了,如何从自动释放池里移除?熟悉 RunLoop 的同窗应该知道,在 RunLoop 唤醒和即将睡眠状态之间会被插入自动释放池,每次 RunLoop 迭代都会向本次迭代加入的对象发送一条release
消息。若是对象的引用计数变为 0,便会被释放。源码分析
自动释放池虽然被叫作”池“,其实它是一个栈结构。栈的实现方式有不少,自动释放池采用了双向链表,可以比较方便的实现 push 和 pop。测试
自动释放池之全部用栈实现,而不用其余数据结构,好比说散列表?是由于 autorelease 对象每每须要批量处理,好比说一次 RunLoop 迭代生成一大批的 autorelease 对象。ui
因此这里就出现一个问题,假如某一段代码在一次 RunLoop 周期内生成大量的 autorelease 对象,尚未等到迭代结束清理,就已经内存溢出了,该怎么办?spa
这是就须要手动的来触发 autorelease 对象的释放。@autoreleasepool{}
登场,它可以控制 autorelease 对象释放的颗粒度。设计
{
for (NSInteger i = 0; i < 10000; i++) {
@autoreleasepool {
NSImage *img = [NSImage imageNamed:@"aimge"];
}
}
}
复制代码
上述的极端例子,若是for
循环中不嵌套 autoreleasepool,在 Xcode 侧边栏的 Debug Session,能看到应用的占用的内存不断的增长。指针
在嵌套使用 autoreleasepool 的场景,而且须要由内而外逐层清理,因此使用栈最适合不过了。
之前面试几乎每次都会被问对象是何时释放的,通常的回答是引用计数为 0,但这只是站在对象的角度考虑的,那何时对象的引用计数会变为 0 呢?
由于了解过自动释放池,因此会说是在 autoreleasepool pop 的时候,若是没有手动的添加 autoreleasepool 便会在 RunLoop 迭代的时候引用计数被减为 0 时释放。
其实回答的有些片面,并非全部对象都是在 pop 的时候引用计数才会为 0 的,普通局部对象其实在超出做用域时(大括号)引用计数为 0,就会被当即释放。
override func viewDidLoad() {
super.viewDidLoad()
let o = NSObject()
}
// `o` has release
复制代码
override func viewDidLoad() {
super.viewDidLoad()
let imgO = UIImage(named: “image”);
}
// imgO 还未释放,会等到 autoreleasepool pop 才释放。
// 可以使用 weak 指针测试,在 viewWillApper 方法该对象仍然存在
// 或使用符号断点,检测 AutoreleasePoolPage::autorelease() 方法被调用
复制代码