objective-C 的内存管理之-自动释放池(autorelease pool)

 

 


如果一个对象的生命周期显而易见,很容易就知道什么时候该new一个对象,什么时候不再需要使用,这种情况下,直接用手动的retain和release来判定其生死足矣。但是有些时候,想知道某个对象在什么时候不再使用并不那么容易。如果下面的代码,看上去非常简单:

Sample.h类接口部分



#import < Foundation / Foundation.h >
@interface Sample : NSObject {

}

-(NSString*) toString;

@end



Sample.m 类实现部分



#import "Sample.h"

@implementation Sample


-(NSString*) toString
{
	NSString* str = [[NSString alloc] initWithFormat:@"This is %@ class",@"Sample"];
	return str;
}
@end



在main函数中调用



#import <Foundation/Foundation.h>
#import "Sample.h"

int main (int argc, const char * argv[]) {	
	
	Sample *s = [Sample new];
	NSLog(@"%@",[s toString]);
	[s release];
	
    return 0;
	
    
}



不知道您是否意识到这段代码有内存泄漏

要纠正这个错误,main函数应该改成:



int main (int argc, const char * argv[]) {	
	
	Sample *s = [Sample new];
	NSString* str = [s toString];
	NSLog(@"%@",str);
	[str release];//手动释放NSString字符串实例
	[s release];
	
    return 0;	
    
}



这种隐藏的错误很容易发生,也容易被忽视。为此obj-c 引用了自动释放池(autorelease pool),每次用xcode创建项目时,可能大家已经注意到了有类似下面的代码模板:



int main (int argc, const char * argv[]) {	
	
	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	//insert code here...
	NSLog(@"Hello,World!");
	
	[pool drain];	
    return 0;	
    
}



即:xcode为开发者写的代码外层包了一层NSAutoreleasePool 。这个池(pool)类似数据结构中的堆栈(Stack),相当于一个容器,每次对象调用autorelease方法时(obj-c中的正式说法应该是:对象发送autorelease消息),对象的引用计数并不 真正变化

下面看一下基本的使用,先给Sample添加一个属性int型的flag(用于在销毁时看到是哪一个实例正在被销毁),同时重写dealloc()以便在释放时能输出一些信息

Sample.h



#import <Foundation/Foundation.h>

@interface Sample : NSObject {	

}

-(NSString*) toString;

//增加一个int型的属性flag
@property int flag;

@end



Sample.m



#import "Sample.h"

@implementation Sample

//自动生成属性flag的setter与getter方法
@synthesize flag;

-(NSString*) toString
{
	NSString* str = [[NSString alloc] initWithFormat:@"This is Sample%d",flag];
	return str;
}


-(void)dealloc
{
	NSLog(@"Sample %d is going to die.",flag);
	[super dealloc];
}
@end



使用自动释放池后的main函数



#import <Foundation/Foundation.h>
#import "Sample.h"

int main (int argc, const char * argv[]) {	
	
	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	Sample *s1 = [Sample new];
	Sample *s2 = [Sample new];
	
	s1.flag=1;
	s2.flag=2;
	
	[s1 autorelease];
	[s2 autorelease];
	
	
	[pool drain];	
    return 0;	
    
}



运行结果为:

2011-02-24 13:28:09.759 MemoryManage[282:a0f] Sample 2 is going to die.
2011-02-24 13:28:09.769 MemoryManage[282:a0f] Sample 1

从结果上看,pool是后进先出的,即:最后autorelease的最先释放(符合stack数据结构的特征)。再回到前面提到的toString方法中内存泄漏的问题,明白pool的基本原理后,只要把return str 换成retrun [str autorelease]

自动释放池从功能上可以理解为一种延时释放

最后提几点注意事项

1、

2、



#import <Foundation/Foundation.h>
#import "Sample.h"

int main (int argc, const char * argv[]) {	
	
	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	
	Sample *s1 = [Sample new];//retainCount=1
	Sample *s2 = [Sample new];//retainCount=1
	
	s1.flag=1;
	s2.flag=2;
	
	NSLog(@"s1.retainCount=%d,s2.retainCount=%d",[s1 retainCount],[s2 retainCount]);
	
	[s1 autorelease];//retainCount仍为1
	
	NSLog(@"s1.retainCount=%d,s2.retainCount=%d",[s1 retainCount],[s2 retainCount]);
	
	[s2 retain];//retainCount=2
	[s2 autorelease];//retainCount仍为2	
	
	NSLog(@"s1.retainCount=%d,s2.retainCount=%d",[s1 retainCount],[s2 retainCount]);	
	
	[pool drain];	
   
	return 0;	
    
}



运行结果:

2011-02-24 13:49:22.558 MemoryManage[461:a0f] s1.retainCount=1,s2.retainCount=1
2011-02-24 13:49:22.566 MemoryManage[461:a0f] s1.retainCount=1,s2.retainCount=1
2011-02-24 13:49:22.567 MemoryManage[461:a0f] s1.retainCount=1,s2.retainCount=2
2011-02-24 13:49:22.578 MemoryManage[461:a0f] Sample 1 is going to die.

因为s2发送了retain消息,从而让其引用计数上升为2,所以最终即使pool释放后,s2仍未销毁。

3、 在iphone/ipad等内存有限的手持设备上,并不建议

4、



int main (int argc, const char * argv[]) {	
	
	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];	
		
	int i;
	
	for	(i=0;i<10000;i++){
		Sample *s = [Sample new];
		s.flag = i;
		NSLog(@"%@",[s toString]);
		[s autorelease];
	}
   
	[pool release];
	return 0;	
    
}



上面的代码运行时,必须等整个循环结束后才开始销毁对象。可以改进为下面这样:



int main (int argc, const char * argv[]) {	
	
	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];	
		
	int i;
	
	for	(i=0;i<10000;i++){
		Sample *s = [Sample new];
		s.flag = i;
		NSLog(@"%@",[s toString]);
		[s autorelease];
		if (i % 100==0)
		{
			[pool release];
			pool = [[NSAutoreleasePool alloc] init];
		}
	}
   
	[pool release];
	return 0;	
    
}



这样每当池子里有100个对象时,就释放一次,这样程序在运行时占用的内存就会少很多

最后从书上抄一段号称Cocoa内存管理的黄金定律:如果我使用了new、alloc或copy方法获得一个对象,则我必须释放(release)或自动释放(autorelease)该对象


作者: 菩提树下的杨过

本文版权归作者有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。