自动释放池@autoreleasepool的实现原理

1、介绍

@autoreleasepool {}主要作用是,将{}里面的autorelease对象做内存管理,在{}即将退出时销毁(Release)autorelease对象。

例如:

自动释放池@autoreleasepool的实现原理_第1张图片

举例代码:cat对象是在---1---与---2---之间释放的,也就是在@autoreleasepool {}里面即将退出时,释放了cat对象

自动释放池@autoreleasepool的实现原理_第2张图片

运行结果:

自动释放池@autoreleasepool的实现原理_第3张图片

2、实现原理

@autoreleasepool {}代码转成C++代码如下


自动释放池@autoreleasepool的实现原理_第4张图片


自动释放池@autoreleasepool的实现原理_第5张图片
__AtAutoreleasePool结构体

从代码代码可以看出__AtAutoreleasePool结构体里面就两个函数:1、默认的构造函数 2、默认的析构函数

从objc的源码中找objc_autoreleasePoolPush()函数及释放对象调用的函数objc_autoreleasePoolPop(atautoreleasepoolobj)。

objc_autoreleasePoolPush()函数实现如下:


自动释放池@autoreleasepool的实现原理_第6张图片

里面包含一个AutoreleasePoolPage的class,其结构如下


自动释放池@autoreleasepool的实现原理_第7张图片
自动释放池@autoreleasepool的实现原理_第8张图片

从截图源码可以看出AutoreleasePoolPage是@autoreleasepool{}底层的核心实现。开辟的内存空间大小SIZE=4096Byte(PAGE_MAX_SIZE)也就是4KB。里面的内存空间除了存放自身内部的成员内容,剩下的空间用来存放autorelease对象的地址。


自动释放池@autoreleasepool的实现原理_第9张图片
AutoreleasePoolPage的size

AutoreleasePoolPage的结构中push() 函数实现如下:

里面调用autoreleaseFast(POOL_BOUNDARY)函数来开辟一页AutoreleasePoolPage。


自动释放池@autoreleasepool的实现原理_第10张图片


自动释放池@autoreleasepool的实现原理_第11张图片

autoreleaseFast函数里面。将相关autorelease对象,加入到page表中。

AutoreleasePoolPage内部的两个成员

从AutoreleasePoolPage内部这两个成员可以知道,每个page直接是通过这两个指针来相互快速定位的,从这层结构可以看出,AutoreleasePoolPage是一个双向链表的结构。


自动释放池@autoreleasepool的实现原理_第12张图片

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会里面。

具体实现源码如下:

自动释放池@autoreleasepool的实现原理_第13张图片

最开始创建好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对象地址。

实现代码如下:


自动释放池@autoreleasepool的实现原理_第14张图片

(2)AutoreleasePoolPage是如何释放里面的对象?

当@autoreleasepool {}即将结束是,会调用objc_autoreleasePoolPop(atautoreleasepoolobj); 执行AutoreleasePoolPage::pop(ctxt);函数,后进入的autorelease对象会先释放,对象依次释放直到遇到POOL_BOUNDARY。表示相应的@autoreleasepool {} 里面的autorelease对象释放完了,然后如果page还有autorelease对象,继续释放,直到遇到最顶层的@autoreleasepool {} 的POOL_BOUNDARY,表示当前整个page里面的autorelease对象都释放完了。一个page里面都释放完了然后接着释放前一个page对象(parent)里面的对象。

释放代码如下:


自动释放池@autoreleasepool的实现原理_第15张图片

3、主线程runLoop与@autoreleasepool {}

app项目,main.m文件里面代码如下:

自动释放池@autoreleasepool的实现原理_第16张图片

从代码看出,我们的APP主线程是运行在一个@autoreleasepool {}里面,也就是这里面的相关autorelease对象,是在@autoreleasepool {}的AutoreleasePoolPage中管理释放的。

那么问个问题,viewDidLoad,viewWillAppear里面创建的autorelease局部对象是不是在调用执行完后里面释放呢?

代码验证如下:


自动释放池@autoreleasepool的实现原理_第17张图片

从代码执行结果可以看到,cat对象的释放并不是在viewDidLoad执行完毕立马释放的,是在viewWillAppear执行后才释放。这个很好的说明,主线程runloop里面产生的autorelease对象,是被加到了page里面管理的。

那么问题来了,page管理的主线程runloop的autorelease对象,是什么时候释放呢?

这个就与runloop有直接的关系,在主线程runloop里面,把主线程runLoop打印看下里面的观察者对象,里面会有两个观察者分别对runloop的进入,休眠,退出进行状态监听,然后处理autorelease对象。

runLoop打印代码

自动释放池@autoreleasepool的实现原理_第18张图片

通过观察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进入休眠时来释放。

你可能感兴趣的:(自动释放池@autoreleasepool的实现原理)