1、介绍
@autoreleasepool {}主要作用是,将{}里面的autorelease对象做内存管理,在{}即将退出时销毁(Release)autorelease对象。
例如:
举例代码:cat对象是在---1---与---2---之间释放的,也就是在@autoreleasepool {}里面即将退出时,释放了cat对象
运行结果:
2、实现原理
@autoreleasepool {}代码转成C++代码如下
从代码代码可以看出__AtAutoreleasePool结构体里面就两个函数:1、默认的构造函数 2、默认的析构函数
从objc的源码中找objc_autoreleasePoolPush()函数及释放对象调用的函数objc_autoreleasePoolPop(atautoreleasepoolobj)。
objc_autoreleasePoolPush()函数实现如下:
里面包含一个AutoreleasePoolPage的class,其结构如下
从截图源码可以看出AutoreleasePoolPage是@autoreleasepool{}底层的核心实现。开辟的内存空间大小SIZE=4096Byte(PAGE_MAX_SIZE)也就是4KB。里面的内存空间除了存放自身内部的成员内容,剩下的空间用来存放autorelease对象的地址。
AutoreleasePoolPage的结构中push() 函数实现如下:
里面调用autoreleaseFast(POOL_BOUNDARY)函数来开辟一页AutoreleasePoolPage。
autoreleaseFast函数里面。将相关autorelease对象,加入到page表中。
从AutoreleasePoolPage内部这两个成员可以知道,每个page直接是通过这两个指针来相互快速定位的,从这层结构可以看出,AutoreleasePoolPage是一个双向链表的结构。
id* next:指向了下一个能存放autorelease对象地址的区域 。
AutoreleasePoolPage只能数一一个thread,不能跨线程。
前面构建page的代码中可以看到autoreleaseFast(POOL_BOUNDARY)。POOL_BOUNDARY这个参数。POOL_BOUNDARY定义为nil,占用8个字节。起到一个page内存放autorelease对象的边界作用。
(1)那么AutoreleasePoolPage如何实现obj自动释放对象的存储呢?
每当obj对象在调用autorelease方法时,会调用autoreleaseFast(obj)把obj加入到AutoreleasePoolPage会里面。
具体实现源码如下:
最开始创建好AutoreleasePoolPage后,会在剩余内存的开始就用来存放autorelease对象地址。依次压入数据栈中,最先入栈的最后释放。第一个autorelease对象是存放在page成员变量及POOL_BOUNDARY(nil)之后的那块空间,具体运算是通过page的结构体地址(首地址)+结构体size大小来得到一块空间地址,这块空间地址首次加入的是autoreleaseFast(POOL_BOUNDARY)。之后依次是autorelease对象,一个page4086剩余的字节都加满autorelease对象后,还有autorelease对象需要管理,则new一个新的page继续接着存放。@autoreleasepool { @autoreleasepool {} }里面嵌套@autoreleasepool {}的情况?怎么存储? 在每一个@autoreleasepool {}开始时,都会调用autoreleaseFast(POOL_BOUNDARY),如果一个page没有放满autorelease对象,则不会新开page来存放嵌套的@autoreleasepool {} 里面的autorelease对象,而是接着在这个page里面以autoreleaseFast(POOL_BOUNDARY),再次加入POOL_BOUNDARY作为一个标记边界,然后依次存放嵌套@autoreleasepool {}的autorelease对象地址。
实现代码如下:
(2)AutoreleasePoolPage是如何释放里面的对象?
当@autoreleasepool {}即将结束是,会调用objc_autoreleasePoolPop(atautoreleasepoolobj); 执行AutoreleasePoolPage::pop(ctxt);函数,后进入的autorelease对象会先释放,对象依次释放直到遇到POOL_BOUNDARY。表示相应的@autoreleasepool {} 里面的autorelease对象释放完了,然后如果page还有autorelease对象,继续释放,直到遇到最顶层的@autoreleasepool {} 的POOL_BOUNDARY,表示当前整个page里面的autorelease对象都释放完了。一个page里面都释放完了然后接着释放前一个page对象(parent)里面的对象。
释放代码如下:
3、主线程runLoop与@autoreleasepool {}
app项目,main.m文件里面代码如下:
从代码看出,我们的APP主线程是运行在一个@autoreleasepool {}里面,也就是这里面的相关autorelease对象,是在@autoreleasepool {}的AutoreleasePoolPage中管理释放的。
那么问个问题,viewDidLoad,viewWillAppear里面创建的autorelease局部对象是不是在调用执行完后里面释放呢?
代码验证如下:
从代码执行结果可以看到,cat对象的释放并不是在viewDidLoad执行完毕立马释放的,是在viewWillAppear执行后才释放。这个很好的说明,主线程runloop里面产生的autorelease对象,是被加到了page里面管理的。
那么问题来了,page管理的主线程runloop的autorelease对象,是什么时候释放呢?
这个就与runloop有直接的关系,在主线程runloop里面,把主线程runLoop打印看下里面的观察者对象,里面会有两个观察者分别对runloop的进入,休眠,退出进行状态监听,然后处理autorelease对象。
runLoop打印代码
通过观察runLoop的进入,休眠,退出状态,然后调用_wrapRunLoopWithAutoreleasePoolHandler()函数来对autorelease对象的释放。函数内部实现也会来到AutoreleasePoolPage的pop函数,在里面对obj做release操作。
4、总结
1)自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage
2)调用了autorelease的对象都是通过AutoreleasePoolPage对象来管理的
3)每个AutoreleasePoolPage对象占用4096 byte内存,用来存放它内部的成员变量及剩下的空间用来存放autorelease对象的地址
4)所有AutoreleasePoolPage对象通过双向链表的形式连接在一起
5)AutoreleasePoolPage调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址,然后后面内存接着存放autorelease的对象地址。(autorelease对象入栈)
6)调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY表示当前page中一个@autoreleasepool {}域内的对象释放完了。(autorelease对象出栈)
7)id*next指向了下一个能存放autorelease对象地址的区域 。通过next来对obj入栈。
利用--next指针运算,对obj出栈释放。 最先入栈的obj最后释放(先入后出)。
8)比如主线程中如果某些操作会产生很多临时对象,比如在循环语句,每次循环产生许多临时对象,为了能够更好更快的管理这部分对象释放,可以局部利用自动释放池更快的去释放对象,而不是等到runloop进入休眠时来释放。