1、autorelease
由于在MRC环境下,LLVM编译器不会帮我们加上retain、release等这些对引用计数操作的方法。
所以下面,我们在MRC环境下来看一段代码。
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"step---1");
@autoreleasepool {
Person *per = [[Person alloc] init];
}
NSLog(@"step---2");
}
NSLog(@"step---3");
return 0;
}
打印结果:并没有打印person dealloc
====================================
step---1
step---2
step---3
在创建Person的时候加上autoRelease
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"step---1");
@autoreleasepool {
Person *per = [[[Person alloc] init] autorelease];
}
NSLog(@"step---2");
}
NSLog(@"step---3");
return 0;
}
打印结果:
====================================
step---1
person dealloc
step---2
step---3
由上面的两段代码可以看出autorelease的作用:
他能够保证autorelease对象在对应的自动释放池销毁时,对autorelease对象进行一个release操作。这样能够管理内存,而且还避免手动调用release方法。
2、 自动释放池@autoreleasepool { }对应__AtAutoreleasePool
NSAutoreleasePool实际上是个对象引用计数自动处理器,在官方文档中被称为是一个类。
使用clang命令编译main.m文件:
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
查看对应的.cpp代码,可以看出@autoreleasepool对应处是声名了一个局部变量__autoreleasepool,他的类型是结构体__AtAutoreleasePool。
__AtAutoreleasePool __autoreleasepool;//声名的结构体变量
//__AtAutoreleasePool结构体
struct __AtAutoreleasePool {
__AtAutoreleasePool() {//构造函数,在创建结构体的时候调用
atautoreleasepoolobj = objc_autoreleasePoolPush();
}
~__AtAutoreleasePool() {//析构函数,在结构体销毁的时候调用
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
void * atautoreleasepoolobj;
};
结合上面的.cpp源码就可以得知:@autoreleasepool { }这样的自动释放池,对应的底层是一个__AtAutoreleasePool类型的局部变量。
当进入作用域(自动释放池起始处)时,创建__autoreleasepool变量,会调用他的构造函数,从而调用objc_autoreleasePoolPush方法。当离开作用域(自动释放池结束处)时,变量__autoreleasepool会被释放(__autoreleasepool是个局部变量),从而调用objc_autoreleasePoolPop方法。
3、AutoreleasePoolPage
在objc_autoreleasePoolPush和objc_autoreleasePoolPop方法中,有个很重要的AutoreleasePoolPage。调用了autorelease的对象最终都是通过AutoreleasePoolPage来管理的。
#pragma mark -AutoreleasePoolPage的内部结构
class AutoreleasePoolPage
{
static pthread_key_t const key = AUTORELEASE_POOL_KEY; //用于创建线程的标识,应该是AutoreleasePoolPage不能同时用在两个线程中
static uint8_t const SCRIBBLE = 0xA3; //刷子,可以不需要关心
static size_t const SIZE = PAGE_MAX_SIZE //4096Bytes
static size_t const COUNT = SIZE / sizeof(id); //4096/8
magic_t const magic; //用于验证 AutoReleasePoolPage 的完整性
id *next; //栈顶指针,指向了下一个能存放autorelease对象地址的区域
pthread_t const thread; //当前线程
AutoreleasePoolPage * const parent; //父节点, AutoReleasePoolPage 是一个双向链表,这个parent也就是指向上一个节点
AutoreleasePoolPage *child; //子节点(下一个节点)
uint32_t const depth; //深度,标识这是第几个节点,从0开始计数
uint32_t hiwat; // high water mark 高水位线,用来报警内存占用
......
}
每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址。
4、autorelease原理
在了解了autoreleasepool和AutoreleasePoolPage后,我们来看一下autorelease的作用流程。
在上面,我们看到了一个自动释放池autoreleasepool创建,就对应生成一个__autoreleasepool对象。在其作用域内,这个对象的创建和销毁分别对应着__AtAutoreleasePool这个结构体的构造函数push和析构函数pop。
autoreleasePoolPush操作
void *objc_autoreleasePoolPush(void){
return AutoreleasePoolPage::push();//c++类的push方法
}
//push方法内部逻辑
static inline void *push() {
id *dest;
if (DebugPoolAllocation) {
dest = autoreleaseNewPage(POOL_BOUNDARY); //创建一个AutoreleasePoolPage,并将这个POOL_BOUNDARY入栈存到page表里
} else {
dest = autoreleaseFast(POOL_BOUNDARY);//本来就有page表的话,将POOL_BOUNDARY存入到这个page表中
}
assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
autoreleasePoolPop操作
void objc_autoreleasePoolPop(void *ctxt) {
AutoreleasePoolPage::pop(ctxt);////c++类的pop方法
}
//push方法内部逻辑
static inline void pop(void *token) //token值应该是当初push进去存放的那个POOL_BOUNDARY值
{
AutoreleasePoolPage *page;
id *stop;
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
if (hotPage()) {
pop(coldPage()->begin());
} else {
setHotPage(nil);
}
return;
}
page = pageForPointer(token);
stop = (id *)token;
if (*stop != POOL_BOUNDARY) {
if (stop == page->begin() && !page->parent) {
// Start of coldest page may correctly not be POOL_BOUNDARY:
// 1. top-level pool is popped, leaving the cold page in place
// 2. an object is autoreleased with no pool
} else {
// Error. For bincompat purposes this is not
// fatal in executables built with old SDKs.
return badPop(token);
}
}
if (PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);//对存在page表中的对象进行release,直到遇见POOL_BOUNDARY值
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
//处理一些跨页page存储对象的问题
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
} else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
} else if (page->child->child) {
page->child->child->kill();
}
}
}
autorelease操作
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);//AutoreleasePoolPage中的autorelease方法,this是调用autorelease方法的对象
}
//autorelease方法内部逻辑
public: static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
//将对象的内存地址放进page表中,如果page表已满,就创建新表,否则直接加入到当前page表
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
//将调用autorelease方法的对象的内存地址放进page表中
//是否新建page表视当前情况而定
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();//取当前page表
if (page && !page->full()) {
return page->add(obj);
} else if (page) {
return autoreleaseFullPage(obj, page);
} else {
return autoreleaseNoPage(obj);
}
}
原理步骤:
- 调用push方法将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
- 在@autoreleasepool { }内调用了autorelease方法的对象,会将他们存入到autoreleasePage表中,会有多张autoreleasePage表双向链接来管理
- 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
5、autorelease和runloop
当程序运行时,主线程会默认创建并且运行一个NSRunloop。除了AppDelegate.m文件中的main函数生成了@autoreleasepool外,ARC会在合适的地方添加@autoreleasepool来管理对象。
@autoreleasepool和runloop互相协作,在每个runloop迭代中都加入自动释放池的push和pop操作来管理对象变量。
runloop的运行逻辑
@autoreleasePool创建与释放时机与runloop的observers相关(observers是监听runloop的状态的,比如进入、退出、休眠、唤醒等),当observer监听到了相应的事件会调用autoreleasepool的push、pop方法。
- 总结:
iOS在主线程的Runloop中注册了2个Observer:
- 第1个Observer
监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush() - 第2个Observer
监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
6、总结
<1>Autorelease对象什么时候会被调用release?
在没有手动加@autoreleasepool的情况下,autorelease对象是在当前的runloop进入休眠(kCFRunLoopBeforeWaiting)或者退出(kCFRunLoopBeforeExit)时释放的。而autoRelease对象能够释放是因为系统在每个runloop的kCFRunLoopEntry、kCFRunLoopBeforeWaiting、kCFRunLoopBeforeExit节点上会调用自动释放池的push和pop方法。<2>方法里有局部对象, 出了方法后会立即释放吗?
MRC环境下创建局部对象并调用autoRelease
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"ViewController");
Person *per = [[[Person alloc] init] autorelease];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(@"viewWillAppear");
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSLog(@"viewDidAppear");
}
打印结果:
====================================
ViewController
viewWillAppear
person dealloc //person并不是一出作用域就释放的
viewDidAppear
ARC环境下自动管理局部对象
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"ViewController");
Person *per = [[Person alloc] init];
}
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(@"viewWillAppear");
}
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
NSLog(@"viewDidAppear");
}
打印结果:
====================================
ViewController
person dealloc //person一出作用域就释放的
viewWillAppear
viewDidAppear
从上面的例子也可以看出,与MRC环境下的结果不同,ARC环境下的局部对象person一出作用域就释放了,这也表明ARC下创建对象时并没有给它加autoRelease,大概是在离开作用域时对person对象进行release操作的。
所以可以说如果是创建局部变量时,对其进行autoRelease操作了,那么局部变量并不是离开方法后就释放了,什么时候释放和runloop什么时候进入休眠有关。而在ARC环境下,由于编译器会对局部变量进行引用计数管理,局部对象出了方法就会释放。