《C#妹妹和Objective-C阿姨对话录》(05)自动释放池--拆迁队的外援

C#妹妹:内存的清理跟生活中的拆迁一样是个麻烦事情啊

Objective-C阿姨:是啊,该拆的不拆,占用空间,不该拆的拆了,程序崩溃

C#妹妹:说白了,难度不在拆上,在判定上,判断到底那些内存是用不上的。

Objective-C阿姨:没错,就像现实生活中的拆迁,扒房子不困难,推土机过来就好了,困难的是决定扒谁的房子。。。扒对了相安无事,扒错了弄个自焚的出来⋯⋯

C#妹妹:做个广告,.NET的垃圾回收机制是相当不错的。判断很准确~

Objective-C阿姨:没错,但是也要付出代价,依靠运行时检查废弃的对象,就好像依靠人口普查来确定那些房子没人用,是靠定时遍历来实现的,毕竟影响性能,并且回收也不可能那么及时。

C#妹妹:是的,垃圾回收其实是两部分工作,一个是“检查”,一个是“回收”,“检查”就是找到那些没人用的房子,在墙上写一个大大的被圆圈圈起来的“拆”字。“回收”就是把标有“拆”字的房子推平,并且把还在用的房子集中在一起,避免形成碎片。还有很重要的一点是,回收过程中程序是暂停状态的。

Objective-C阿姨:写拆字的国际惯例也符合啊?~整个过程好像很漫长,性能如何保证呢?

C#妹妹:.NET的运行时为了提高效率采取了很多方法。

首先它优先普查人口流动比较大的地区,人口流动大,意味着房子闲置的可能性比较大。这个主要通过代龄来优化的,对象占用的空间,每经过一次垃圾回收器的扫描,而没有被清理掉,代龄就加1。比如你04年买的房子,这时代龄为0,06年人口普查发现这套在用,代龄就变成1,08年人口普查有可能就不再检查这套房子了,因为你已经起码住了2年多了,搬家的可能性小些,垃圾回收器会重点检查上次人口普查之后新入住的那些房子。当然清理了0代的房子后,还没有足够的空间,垃圾回收器还会去检查1代甚至2代的房子的。

Objective-C阿姨:这就是歧视啊,怪不得我在上海买了房子也没户口,原来我是0代⋯⋯

C#妹妹:没法子啊,谁让效率优先呢?谁会在乎我们小百姓的公平~垃圾回收器第二个提高效率的方法是减少普查的次数,除非程序占用的内存超过规定,或者系统本身内存不富裕,不会轻易去搞内存普查这些烂事的。你以为垃圾回收器没事干就一直扫描啊,他们也是想多歇歇呢~

Objective-C阿姨:.NET垃圾回收也有这么多内幕啊

C#妹妹:小声点,你不想想,这年头没点内幕谁去拆迁啊?Objective-C阿姨,你的对象管理就没有内幕?

Objective-C阿姨:嗯,有,不过相对比较河蟹~因为有那么点选择的余地,上次不是说过了通过“Retain、Release”统计对象的引用数量来判断对象是否可以回收,这种方法专业一点的名字叫“引用计数(reference counting)技术”。今天我继续往后讲“自动释放(Auto Release)技术”。

C#妹妹:自动释放?听起来好像蛮先进的,是不是跟我的垃圾回收差不多。

Objective-C阿姨:差的多,它的本质还是引用计数,其实并不自动化,只是简化了逻辑和代码而已。算是个外援吧,还拿那个悲剧的House类做个实验吧

#import "House.h"
//先建一个需要被删除的对象 House类
@implementation House
-(void) dealloc//Objective-C在销毁对象的时候会自动调用这个方法
{
    NSLog(@"房子被拆除了");
    [super dealloc];
}
@end

Objective-C阿姨:这个倒霉House类,只重载了dealloc这个方法,忘记了么?就是拆房子的时候会调用这个方法。接下来执行下面的代码

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];//建立一个自动释放池 pool
    House *h1=[[House new] autorelease];//将生成的对象添加到自动释放池中
    NSLog(@"%lu",[h1 retainCount]);//对象的引用数量为1
    NSLog(@"开始销毁自动释放池");
    [pool drain];//销毁自动释放池,同时也销毁自动释放池中的对象,包括h1引用的House对象
    return 0;
}

执行结果

Objective-C阿姨:一句话一句话看吧,首先是

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

听起来貌似很酷,又是自动,又是池,其实没啥技术含量,原理其实是个类似NSMutableArray的东西,它的dealloc方法里有向池中所有对象发送release消息的代码,所以销毁池的时候,会执行池的dealloc方法,池向所有对象发送release。就这么简单。

至于[[NSAutoreleasePool allocinit] 相当于 [NSAutoreleasePool new],就是初始化对象。

说白了就是实例化了类“NSAutoreleasePool”的一个对象

C#妹妹:我看也是,上次你说过的NS开头表示Cocoa的类,这个类就是名字稍微长点,要是竖着写可能还衬的个头高点,可惜是趴着的,确实没觉得比其他类长的帅⋯⋯

Objective-C阿姨:House *h1=[[House new] autorelease]这句话重点在autorelease,意思是把House的实例,添加到NSAutoreleasePool中。也可以这样写

 

 House *h1=[House new];
    [h1 autorelease];

这一步仅仅是把对象添加到池中,并没有减少引用,所以统计retainCount的时候,仍然为1

接下来最后一步

[pool drain]

drain和release意思差不多,就是清空/销毁自动释放池,同时销毁池中的对象。

但是这里说“销毁池中的对象”其实很不准确

准确的说法是逐个给池中的对象发送release消息,在看看刚才我说的原理。

因为经过这一步,池中对象仅仅是引用数量减1(也就是调用了release),能否销毁要看减1后引用数量是否为0

比如看下面的例子

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];//建立一个自动释放池 pool
    House *h1=[[House new] autorelease];//将生成的对象添加到自动释放池中
    [h1 retain];//手工添加一个引用
    NSLog(@"%lu",[h1 retainCount]);//对象的引用数量为2
    NSLog(@"开始销毁自动释放池");
    [pool drain];
    //这个时候h1引用的对象并没有被销毁 发生内存泄露
    NSLog(@"内存泄露啦:%@",h1);
    return 0;
}

执行结果

因为我们之前手工通过retain给h1引用的对象增加了一个引用,导致即使[pool drain]也没有销毁对象

所以说AutoreleasePool并没有帮我们真正的把池中的对象销毁,只是给池中对象逐个调用release而已。

再以图片的形式看看内存中发生的事情


C#妹妹:这样看下来,自动释放池并没有解决什么问题呀?不是还要我们自己管理引用数量么?

Objective-C阿姨:确实还是需要我们自己统计引用数量,但是可以帮我们从乱七八糟的release中解脱出来。只需要release池对象就好了,其他对象也就跟着被release了

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];//建立一个自动释放池 pool
    House *h1=[[House new] autorelease];
    House *h2=[[House new] autorelease];
    House *h3=[[House new] autorelease];
    House *h4=[[House new] autorelease];
    NSLog(@"%lu,%lu,%lu,%lu",[h1 retainCount],[h2 retainCount],[h3 retainCount],[h4 retainCount]);
    //h1-h4经过某些运算 完成了历史使命
    [pool drain];
    return 0;
}

上面的代码肯定比下面的这些更清爽点

int main (int argc, const char * argv[])
{
    House *h1=[House new];
    House *h2=[House new];
    House *h3=[House new];
    House *h4=[House new];
    NSLog(@"%lu,%lu,%lu,%lu",[h1 retainCount],[h2 retainCount],[h3 retainCount],[h4 retainCount]);
    //h1-h4经过某些运算 完成了历史使命
    [h1 release];
    [h2 release];
    [h3 release];
    [h4 release];
    return 0;
}

C#妹妹:貌似是少调用了些release,但是毕竟没有release灵活啊,如果是release,一旦程序员决定不在使用的对象,马上就可以释放掉,但是AutoreleasePool必须要等到最后一起drain。说白了也就是延长了垃圾的生存时间,浪费了内存。

Objective-C阿姨:一点不假,所以在移动设备上 比如iPhone,iPad,为了节约宝贵的内存资源,不建议使用自动释放池。即便在Mac上运行的程序,如果有太多的对象生成,也可以多次生成释放或嵌套对象池来尽早释放对象。比如下面的例子

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int i;
    for(i=0;i<1000000000;i++)
    {
        House *h=[[House new] autorelease];
        if(i%1000==0)//如果没有这个if语句里边的内容,所有1000000000个对象要全部生成完毕才能全部释放
        {
            [pool drain];
            NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
        }
    }
    [pool drain];
    return 0;
}

比如上面的代码,如果没有if语句里边的内容,所有1000000000个House对象都要等到全部对象生成完毕,程序结束的时候才能全部释放

内存消耗量非常大

但是加了这个if语句,每生成1000个对象,销毁一次,内存占用就非常小。

咳,十亿套房子要真的就这么生成了,非气死那些开发商不可。。。

C#妹妹:

Objective-C阿姨:说了半天,还没有说自动释放池最重要的用法。

C#妹妹:最重要??你认为有人会耐着性子把咱俩的对话看到这个地方么?

Objective-C阿姨:刚才说嗨了,重点忘记了。也许有那么1-2人能耐着性子看到这里吧,大部分人早把网页关闭了。看下面的例子

#import "House.h"
//先建一个需要被删除的对象 House类
@implementation House
-(void) dealloc//Objective-C在销毁对象的时候会自动调用这个方法
{
    NSLog(@"房子被拆除了");
    [super dealloc];
}
-(NSString*)description
{
    NSString *desc=[[NSString alloc] initWithFormat:@"世界上最便宜的房子"];
    return desc;
}
@end

我修改了一下House类,添加了一个description方法,返回一个NSString对象,

description相当于.NET中的ToString()方法,其实也是基类NSObject定义的,子类可以重写,用来返回描述该类的字符串。

我这里重新了description,返回NSString对象,但是这个NSString对象该如何销毁呢?该由谁来负责release呢?

显然这个工作只能由调用description的程序来完成

C#妹妹:废话,要是在description里边就销毁了,还返回个屁结果啊

Objective-C阿姨:那就看下面的例子吧。

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    House *h1=[[House new]autorelease];
    NSString *desc=[h1 description];
    NSLog(@"%@",desc);
    [desc release];
    [pool drain];
    return 0;
}

这样用起来毕竟不直接

如果在House的description中使用autorelease,比如代码修改为

NSString *desc=[[NSString alloc] initWithFormat:@"世界上最便宜的房子"];
return [desc autorelease];

调用House的description的程序就可以更简洁

int main (int argc, const char * argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    House *h1=[[House new]autorelease];
    NSLog(@"%@",[h1 description]);
    [pool drain];
    return 0;
}

C#妹妹:今天好像好像晕了⋯⋯感觉越来越复杂了~~,程序员管理内存还真是很辛苦⋯⋯

Objective-C阿姨:今天说了太多的技术,因为没有规则来约束他们,所以感觉好像杂乱无章,下次会有内存管理的章法,只要遵循几个简单的原则,其实是非常简单的。下次再聊哇。。。

--

各位同学,本人学习Objective-C时间很短,学习Objective-C其实不是为了Mac、iPhone开发,并没有实用,
其实是一个C#用户学习Objective-C的学习笔记,学习的确切目的是帮助我理解C#,毕竟没有比较是不可能知道所谓C#的特点的
请大家批判的眼光看这个东西,如果发现和其他文章、书籍、评论、资料有冲突,请尽量以其他文章为准。并给我留言
也邀请所有高手积极拍砖,我正好用来盖房子~~~
《C#妹妹和Objective-C阿姨对话录》

(01)认识Objective-C--初次见面的问候 
(02)这就是类--阿姨的狗狗 
(03)NSString--再遇狗狗
(04)垃圾回收基础--拆迁队那点事

(05)自动释放池--拆迁队的外援 

        待续⋯⋯

posted @ 2011-04-28 09:24  小墨的童鞋  阅读(7038)  评论(29编辑  收藏  举报