最近工作闲下来,回头看了看关于内存管理方面的东西,其中重点看了下autorelease。
autorelease,顾名思义,就是自动释放,看上去很ARC,其实它更像C语言中的自动变量(当某自动变量超出其作用域,该变量会被自动废弃)
{
int index;
} /* 因为超出变量作用域,自动变量index被废弃,不可访问*/
autorelease类似于C语言的自动变量那样对待对象实例,当超出其作用域,对象实例的release方法被调用。
autorelease的具体使用:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
NSObject *object = [NSObject alloc]init];
[object autorelease];
[pool drain];
其中最后的“ [pool drain] 类似于 [object release] ”
通过以上代码,我们可以知道,只要不废弃NSAutoreleasePool,那么生成的对象都不会被废弃,因此有时候会产生内存不足的现象。比如在一个for循环中,大量创建autorelease对象,这个时候就必须在for循环内部适当的地方生成、持有或废弃。
if (int i = 0; i < 10000; i++){
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// ... 大量产生autorelease对象
[pool drain];
}
但是正常我们编写代码,很少使用NSAutoreleasePool来释放对象。
那是因为在cocoa框架中,主线程中的Runloop,会对NSAutoreleasePool对象进行生成、持有和废弃。
每个runloop中都创建一个Autorelease Pool,并在runloop的末尾进行释放,
所以,一般情况下,每个接受autorelease消息的对象,都会在下个runloop开始前被释放。也就是说,在一段同步的代码中执行过程中,生成的对象接受autorelease消息后,一般是不会在代码段执行完成前释放的。
autorelease的具体实现:
autorelease的具体实现,在objc4库 runtime/NSobject.mm类中有介绍,objc4库开源,源码在这里可以下载https://github.com/opensource-apple/objc4。
介于C++代码实现较为复杂,我们先看看GNUstep的实现。
[object autorelease];
这里调用NSObject类的autorelease实例方法。
GNUstep/modules/core/base/Source/NSObject.m autorelease
- (id)autorelease {
[NSAutoreleasePool addObject:self];
}
autorelease方法本质就是调用NSAutoreleasePool的addObject类方法。
接下来看看NSAutoreleasePool类方法addObject的实现。
GNUstep/modules/core/base/Source/NSAutoreleasePool.m
+ (void)addObject:(id)anObj {
NSAutoreleasePool *pool = 取得正在使用的NSAutoreleasePool对象;
if (pool) {
[pool addObject:anObj];
}
}
addObject类方法,调用正在使用的NSAutoreleasePool对象的实例方法。
接下来看看addObject实例方法的实现
- (void)addObject:(id)anObj {
[array addObject:anObj];
}
说明如果调用NSObject类的autorelease实例方法,最终该对象会被添加到正在使用的NSAutoreleasePool对象中的数组里。
最后看看被添加进去的对象是如何被释放的:[pool drain]
- (void)drain {
[self dealloc];
}
- (void)dealloc {
[self emptyPool];
[array release];
}
- (void)emptyPool {
for (id obj in array) {
[obj release];
}
}
虽然调用了好几个方法,但我们能看到,被添加到NSAutoreleasePool数组中的所有对象都调用了release实例方法,都会被释放掉。
看到这里,大家应该大致清楚autorelease内部实现,但这是GNUstep的实现,我们再来看看Apple的实现。具体可通过查看objc4库 runtime/NSObject.mm 中的AutoreleasePoolPage类来确认。
这个类有大概近500行代码,但最主要的其实就其中五个方法。
class AutoreleasePoolPage {
static inline void *push {
// 相当于生成或持有NSAutoreleasePool类对象
}
static inline void *pop(void *token) {
// 相当于废弃NSAutoreleasePool类对象
releaseAll();
}
static inline id autorelease (id obj) {
// 相当于NSAutoreleasePool类的addObject类方法
AutoreleasePoolPage *autoreleasePoolPage = 取得当前正在使用的AutoreleasePoolPage实例;
autoreleasePoolPage->add(obj);
}
id *add (id obj) {
// 相当于调用NSAutoreleasePool类的addObject实例方法,将对象追加到内部数组中
}
void releaseAll () {
// 调用内部数组中对象的release实例方法,类似GNUstep中的emptyPool方法
}
};
根据AutoreleasePoolPage类中的5个方法,NSObject类中实现了以下三个方法:
void *objc_autoreleasePoolPush(void) {
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt) {
if (UseGC) return;
AutoreleasePoolPage::pop(ctxt);
}
id *objc_autorelease(id obj) {
return AutoreleasePoolPage::autorelease(obj);
}
我们再返回来看看这段代码:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
/* 等同于objc_autoreleasePoolPush()*/
NSObject *object = [NSObject alloc]init];
[object autorelease];
/* 等同于objc_autorelease(obj)*/
[pool drain];
/* 等同于objc_autoreleasePoolPop(pool)*/
读到这里,相信读者对autolerease的使用和内部实现都有了一个非常清晰的了解。