iOS autoreleasePool 理解

没有在网上找到好的关于autoreleasePool的文章,于是打算自己写一篇。从MRC中的自动引用计数开始写,便于理解ARC中的@autoreleasepool。文章地址
参考书籍:《Objective-C 高级编程》

MRC

autorelease是什么

autorelease会像C语言的自动变量那样来对待实例对象,在超出作用域的时候释放实例对象,不同的是autorelease可以来手动设定作用域的范围。

autorelease的使用

在MRC中我们通常在一下情况中使用autorelease

1
2
3
4
5
-(id)object{
id = [[NSObject alloc] init];
[obj aoturelease]; //这里没有使用NSAutoreleasePool,原因在下面会解释
return obj;
}

autorelease:取得对象的存在,但自己不持有对象。

autorelese提供这样的功能,使对象在超出指定的生存范围时能够自动并正确释放(调用release方法)。
autorelease将对象注册到autoreleasepool中,在pool结束(一般是作用域结束的时候),pool自动调用release

upload successful

autorelease的具体使用过程:

  • 1、生成并持有NSAutoreleasePool对象;
  • 2、调用已分配对象的autoreleae实例方法;
  • 3、废弃NSAutoreleasePool对象。

示例代码:

1
2
3
4
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];  //区别于前面的代码这里使用了NSAutoreleasePool
id = obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain]; //在调用这行代码时相当于调用了所有注册到pool中的对象的release方法。

为什么有时候使用NSAutoreleasePool有时候不使用NSAutoreleasePool

原因:主循环的NSRunLoop会对NSAutoreleasePool进行生成和释放,所以开发者并不是一定得使用NSAutoreleasePool进行开发。也就是说就算你不生成NSAutoreleasePool,运行时也会自动生成一个。

什么时候使用NSAutoreleasePool对象

虽然NSRunloop会对生成的autorelease对象进行释放,但是短时间内生成的大量对象并不会立即释放,如下:

1
2
3
4
5
6
7
for(int i = 0 ; ;i++ ){
/**
* 读取图片
* 对图片进行处理
*/
}
//短时间内生成了大量的对象,并不会立即释放,这时候就会造成内存不足,从而引发卡顿。

加入NSAutoreleasePool可以解决这个问题:

1
2
3
4
5
6
7
8
for(int i = 0 ; ;i++ ){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/**
* 读取图片
* 对图片进行处理
*/
[pool drain];//释放生成的局部变量,就不会造成对象的大量堆积
}

autorelease的实现

1
[obj autorelease];

我们来查看一下以上代码在GNUstep中的源代码

GNUstep是cocoa框架的互换框架。也就是说,GNUStep的源代码虽不能说与苹果的Cocoa实现完全相同,但是从使用者的角度来看,两者的行为和实现方式是一样的,或者说非常相似。理解了GNUstep源代码也就相当于理解了苹果的Cocoa实现。

1
2
3
4
-(id)autorelease
{
[NSAutorelease addObject:self];
}

autorelease实例方法的本质就是调用了NSAutoreleasePool对象的addObject类方法。
addObject方法的实现(源代码比较复杂,此为简化代码):

1
2
3
4
5
6
7
8
+(void)addObject:(id)object{
NSAutoreleasePool *pool = 取得正在使用的NSAutoreleasePool对象;
if(pool != nil){
[pool addObject:object];//pool存在一个array属性来添加所有注册到这个pool中的对象。
}else{
//error
}
}

addObject方法中使用的pool对象正是你刚刚生成的NSAutoreleasePool对象,如果嵌套生成NSAutoreleasePool那么获取的pool就是你最内侧的pool对象,也就是“离你的对象最近的NSAutoreleasePool对象”。

[pool drain];这行代码在运行时会释放所有注册到pool中的对象。
我们来看下drain方法的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-(void)drain
{
[self dealloc];
}
-(void)dealloc
{
[self emptyPool];
[array release];
}
-(void)emptyPool
{
for (id obj in array){
[obj release];
}
}

ARC中autoreleae的使用

ARC有效时我们无法使用autorelease方法,也不能使用NSAutoreleasePool类。但是我们会有书写更方便的替代方法。

使用姿势

ARC无效时:

1
2
3
4
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id = obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];

ARC有效时:

1
2
3
@autoreleasepool{
id obj = [[NSObject alloc] init];
}

ARC有效时基本不会用到__autoreleasing修饰符,这里就不展开了,有疑问的同学可以留言

@autoreleasepool的实现

当你使用@autoreleasepool的时候,编译器会有一份替换代码。
你的代码:

1
2
3
@autoreleasepool{
id obj = [[NSObject alloc] init];
}

编译器的模拟代码:

1
2
3
4
5
id pool = objc_autoreleasePoolPush();
id = objc_msgSend(NSObject,@selector(alloc));
objc_msgSend(obj,@selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);

我们可以看到编译器转换后的代码和我们前面的NSAutoreleasePool类的使用方式一模一样,所以不管ARC是否有效autorelease的功能一模一样。

打赏不在乎金额,是我写作的源动力