zoukankan      html  css  js  c++  java
  • objectiveC 的内存管理之自动释放池(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;
    	
        
    }
    

    不知道您是否意识到这段代码有内存泄漏问题,因为Sample.m的toString方法生成了一个NSString类的实例,但是main函数最后只释放了Sample的实例s,却并未释放这个NSString类的字符串实例!

    要纠正这个错误,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消息),对象的引用计数并真正变化,而是向pool中添加一条记录,记下对象的这种要求。最后当pool发送drain或release消息时,池中的所有对象的这种要求一一被执行(即:pool被销毁前,会通知池中的所有对象,全部发送release消息真正将引用计数减少,如果对象之前有发送过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 is going to die.

    从结果上看,pool是后进先出的,即:最后autorelease的最先释放(符合stack数据结构的特征)。再回到前面提到的toString方法中内存泄漏的问题,明白pool的基本原理后,只要把return str换成retrun [str autorelease]就行了,即把该字符串在池中登记,这样当[pool drain]时,所有登记的对象,将自动调用release方法,从而得到释放。

    自动释放池从功能上可以理解为一种延时释放技术:即通过发送autorelease消息,向自动释放池登记,表明自己将来会在pool销毁时,一并发送release消息销毁自己。

    最后提几点注意事项

    1、NSAutoreleasePool的实例pool本身也是一个对象,同样需要释放,所以最后也同样需要[pool release]或[pool drain],也正是这一行代码,才会将池中的所有对象同时释放。--注:drain仅适用于max os高版本,低版本不适用,而release通用,其它并无太大差别

    2、pool在release时,仅仅只是简单的让所有池中的对象都发送release而已,并无其它玄机。(即:让池中所有对象的引用计数减1) 所以,如果你在之前用代码强制retain了某对象的引用计数,即使pool被release了,池中的对象仍然有可能因为引用计数仍大于1,而未被销毁。比如下面的代码:

    #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等内存有限的手持设备上,并不建议使用autorelease,因为说到底这是一种延时释放,如果你的程序一直在跑,代码尚未执行到[pool release]之前,即使有很多对象不再需要了,但它们占用的内存并未真正释放。

    4、不要把大量循环操作放到同一个NSAutoreleasePool之间,道理同上,这样会使池中有大量对象,导致程序在运行时占用较多内存。比如下面这段代码:

    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)该对象

    作者:菩提树下的杨过
    出处:http://yjmyzz.cnblogs.com
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    4-(基础入门篇)学会刷Wi-Fi模块固件(刷AT指令固件)
    关于后期文章
    11-51单片机ESP8266学习-AT指令(ESP8266作为TCP客户端,连接TCP服务器,用串口调试助手和手机TCP调试助手测试)
    关于单双向晶闸管的控制电路
    3-(基础入门篇)稍微了解一下(需要知道的关于Lua的一些基本的知识)
    2-(基础入门篇)Air202下载开发入门(给Air202下载第一个程序)
    1-51单片机开发板介绍(所讲内容,功能介绍)
    2-关于单片机通信数据传输(中断接收,大小端,IEEE754浮点型格式,共用体,空闲中断,环形队列)
    1-STM32物联网开发WIFI+GPRS(Wi-Fi入门篇)_简介
    unbuntu下安装qq
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/1963526.html
Copyright © 2011-2022 走看看