如果一个对象的生命周期显而易见,很容易就知道什么时候该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,而未被销毁。比如下面的代码:

运行结果:

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

原文:

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

objective-C 的内存管理之-自动释放池(autorelease pool)的更多相关文章

  1. Objective-C----MRC内存管理 、 自动释放池 、 面向对象三大特性及封装 、 继承 、 组合与聚合

    1 MRC练习 1.1 问题 引用计数是Objective-C语言采用的一种内存管理技术,当一个对象被创建在堆上后,该对象的引用计数就自动设置为1,如果在其它对象中的对象成员需要持有这个对象时,则该对 ...

  2. Autorelease自动释放池的使用

    Autorelease自动释放池的使用 使用ARC开发,只是在编译时,编译器会根据代码结构自动添加了retain.release和autorelease. MRC内存管理原则:谁申请,谁释放 遇到al ...

  3. (五十八)NSObject实现多线程、自动释放池的补充

    模拟一个图片下载的场景,图片的下载需要2s,在这期间为了保证程序的流畅,应该把图片的下载放在子线程中进行. 使用NSObject的方法performSelectorInBackground方法即可实现 ...

  4. autoreleasepool 自动释放池的理解

    常见的面试题:以下代码存在什么样的问题?应该如何改进? for (int i = 0; i < 100000; i++) { NSString *str = @"abc"; ...

  5. OC学习篇之---数组对象的引用计数问题和自动释放池的概念

    之前一片文章中我们介绍了OC中的两个关键字@property和@synthesize的使用的使用: http://blog.csdn.net/jiangwei0910410003/article/de ...

  6. OC_内存管理(二)对象复制、循环引用问题、自动释放池

      循环调用: 1.循环引用的问题 两个对象A.B,有可能会出现特殊情况:A中包含B的实例变量:B中也包含A的实例变量,如果这两个实例变量都是强引用(A有着B的实例变量所有权,B也有A的实例变量所有权 ...

  7. Objective c 自动释放池

    学IOS 的大家都知道,IOS 一共有三种内存管理方式:MRC .ARC.自动释放池.我按照我个人的理解简述一下自动释放池,希望能给大家一点帮助,如有错误请大家及时批评指正. 自动释放池有几个特点:1 ...

  8. OC基础15:内存管理和自动引用计数

    "OC基础"这个分类的文章是我在自学Stephen G.Kochan的<Objective-C程序设计第6版>过程中的笔记. 1.什么是ARC? (1).ARC全名为A ...

  9. OC 内存泄露 自动释放池

    花絮:看到下面的代码就想起这么一个调侃: 一个老程序员,功成名就,金盆洗手不在写代码后,决定练练书法.提笔思索良久后在纸上写下:Hello world! /********************** ...

随机推荐

  1. Fluent UDF【6】:预处理命令

    今天要讲的内容是关于C语言的预处理.搞清楚了这个,就可以分析UDF中的各种头文件源代码,从此写UDF不求人. 1 关于预处理 在UDF的各种头文件中(文件路径D:\Program Files\ANSY ...

  2. Fluent UDF【2】:学习途径

    要怎样做才能做到无畏惧编写任何UDF程序?估计很多与UDF打交道的人都会问到这个问题. 面对UDF文档中那众多的宏描述,小伙伴们是不是感觉到茫然无措,不知从何入手.有时候读别人写好的程序感觉并不难,然 ...

  3. 【小白的CFD之旅】23 串行与并行

    夏日的早晨,被酷热和蚊子骚扰了一夜的小白刚进入梦乡就被老蓝的电话给惊醒了. “8点开会!”老蓝说话永远那么简短有力,根本不给人请假的时间就挂了电话. 小白一看时间,都7点半了,得,这懒觉睡不成了.小白 ...

  4. Cowboy实例

    这个例子主要是用cocos2d-x引擎自带的资源 cocos2d-x-2.2.2\samples\Cpp\TestCpp\Resources\armature 新建工程之后 #include &quo ...

  5. psycopg事务

    1.事务的特性 原子性,要么完成,要么都不完成 2.psycopg的事务特点 在psycopg中,事务是由connection处理的,当第一次一个命令发送给数据库时(开启数据库操作游标), 一个事务就 ...

  6. android category

    本章节翻译自<Beginning-Android-4-Application-Development>,如有翻译不当的地方,敬请指出. 原书购买地址http://www.amazon.co ...

  7. CSS实现:标题两侧画线效果

    如图: html代码: <div class="detail-news"> <h5 class="detail-news-title"> ...

  8. linux下查看最后登陆的用户的信息

    [root@oracle ~]# last -aroot pts/1 Wed Apr 1 10:35 still logged in 10.3.12.1输入命令last -a 把从何处登入系统的主机名 ...

  9. 单独的 python 脚本文件使用 django 自带的 model

    django1.9.5&python3.4.4 文件结构 在一个爬虫脚本中将爬取的数据通过django自带的model保存到数据库   修改的文件(其余pycharm新建Django项目生成, ...

  10. s3c2440代码重定位和段的引入——学以致用,综合Makefile的锻炼

    对于2440而言,nand启动,nand的前4k内容由硬件复制到sram. nor flash,可以像内存一样读,但是不能像内存一样写,执行写操作需要特殊的操作. 程序中包含有需要写的全局或者静态变量 ...