1、autoreleasepool总是会被问到,放在自动释放池中的对象合适被释放?理解不正确的答案:{}出了大括号、出了作用域等等。个人认为参考答案是,1、在不是手动添加的AutoreleasePool时,当前的runloop结束时释放,因为编译器在每个runloop中开始时加入了自动释放池的Push和结束时加入了Pop;2、如果是手动添加的AutoreleasePool,就是在@autorelease{}大括号的时候释放,因为编译器在大括号之后调用的Pop。
下面有一个例子:
以上是测试代码。
在ios8以下的运行结果是:
这个运行结果也说明了“出了作用域对象就被释放的说法是不正确的”,同时也能发现,对象在viewWillAppear中还没有被释放,而到了viewDidAppear中的时候对象已经被释放了。
再补一张证明确实是在这两个方法之间被释放的图:
我在viewDidLoad讲要结束的时候设置了一个断点,并且在console中设置了对reference变量值变化的监听。当开始继续执行代码,并且改变reference的值时,上图展示了改变的时间点。从图中可以看到,在viewWillAppear之后reference的值被改为了nil,也就是在viewWillAppear之后自动释放池地方了对象。
注:刚在上面写了在ios8之前的结果,因为从ios9开始编译器对NSString的stringWithFormat方法做了改变(优化),下面附一张ios9的结果图:
2、上一条中提到的Autorelease调用Push和Pop
上图中展示的是AutoreleasePoolPage的结构图,AutoreleasePool是由若干个AutoreleasePoolPage以双向链表组合而成的。每个AutoreleasePoolPage占有空间的大小是虚拟内存一页的大小(4096bytes字节)
magic:用来校验AutoreleasePoolPage结构的完整性
next:最新添加的autoreleased对象的下一个位置
thread:当前的线程
parent:指向父结点
child:指向子结点
depth:代表深度,从0开始向后递增1
hiwat:high water mark
当next == begin(),表明AutoreleasePoolPage为空;当next == end(),表明AutoreleasePoolPage已满,如下图:
当AutoreleasePoolPage满了之后,就会建立下一个AutoreleasePoolPage对象,新的AutoreleasePoolPage的next指针被初始化在栈底(begin()的位置)。
嵌套的AutoreleasePool
前面只介绍了添加对象的情况,下面说一下释放的时刻:
每当进行一次objc_autoreleasePoolPush时,runtime向当前的AutoreleasePoolPage中add一个哨兵对象,值为0(nil),效果如下图:
objc_autoreleasePoolPush的返回值是这个哨兵对象的地址,于是在释放的过程中;
1、根据传入的哨兵对象地址找到哨兵对象所在的AutoreleasePoolPage
2、在当前AutoreleasePoolPage中,将晚于哨兵对象加入的所有对象发送release消息(可以跨越page),然后移动next指针到正确的位置
所以知道了AutoreleasePool释放对象的过程之后,嵌套的AutoreleasePool就好理解多了,多层的AutoreleasePool就是对个哨兵对象,每次pop的时候释放上次push的位置(哨兵对象的位置)。
3、Autorelease的其他
在ARC中使用@autorelease{}时,编译器将 @autorelease{}转化成:
void *context = objc_autoreleasePoolPush();
//{}中的代码
objc_autoreleasePoolPop(context);//如果不是手动添加的autoreleasePool,就是在当前runLoop结束时调用
objc_autoreleasePoolPush()是对AutoreleasePoolPage::push()的封装
objc_autoreleasePoolPop() 是对AutoreleasePoolPage::pop()的封装
参考:http://www.cocoachina.com/ios/20141031/10107.html