1103内存

http://supershll.blog.163.com/blog/static/37070436201203122246143/

第九章 内存管理

9.1 对象生命周期
对象的生命周期包括诞生(通过alloc或new方法实现)、生存(接收消息和执行操作)、交友(借助方法的组合和参数)以及死亡(被释放)。当对象的生命周期结束时,他们的原材料(内存)将被回收以供新的对象使用。
9.1.1 引用计数(reference counting),也叫做保留计数
当某段代码需要访问一个对象时,该代码将该对象的保留计数值+1,结束访问时,保留计数器值-1.当保留计数器值为0时,对象将被销毁。
当使用alloc、new方法或者通过copy消息(生成接收对象的一个副本)创建一个对象时,对象的保留计数器值被设置为1.要增加对象保留计数器值,可以给对象发送一条retain消息。减少使用release。
当一个对象的保留计数器值为0而即将被销毁时,Obj-C自动向对象发送一条dealloc消息。你可以在自己的对象中重写dealloc方法。可以通过这种方法释放已经分配的全部相关资源。一定不要直接调用dealloc方法。可以利用Obj-C在需要销毁对象时调用dealloc方法。要获得保留计数器的当前值,可以发送retainCount消息。
- (id) retain;
- (void) release;
- (unsigned) retainCount;
retain方法返回一个id类型的值,通过这种方式,可以嵌套执行带有其他消息发送参数的保留调用,增加对象的保留计算器值并要求对象完成某种操作。例如:
[[car retain] setTire:tire AtIndex:2];表示要求car对象将其保留计数器值+1,并执行setTire操作。
例子:
@interface RetainTracker:NSObject
@end 

@implementation RetainTracker

- (id) init
{if (self=[super init]){
  NSLog(@"init:Retain count of %d:",[self retainCount]);
 }
 return (self);
}

- (void) dealloc
{
 NSLog(@"dealloc called,Bye bye");
 [super dealloc]; //[super dealloc]一定要放在后面
}
@end


int main(int argc,const char *argv[])
{RetainTracker *tracker=[RetainTracker new];
 //count:1

 [tracker retain];//count:2
 NSLog(@"%d",[tracker retainCount]);

 [tracker retain];//count:3
 NSLog(@"%d",[tracker retainCount]);

 [tracker release];//count:2
 NSLog(@"%d",[tracker retainCount]);

 [tracker release];//count:1
 NSLog(@"%d",[tracker retainCount]);

 [tracker retrain];//count:2
 NSLog(@"%d",[tracker retainCount]);

 [tracker release];//count:1
 NSLog(@"%d",[tracker retainCount]);

 [tracker release];//count:0,dealloc it

 return (0);
}

输出结果为:
init:Retain Count of 1
2
3
2
1
2
1
dealloc called byebye

9.1.2 对象所有权
当我们说某个实体“拥有一个对象”时,就意味着该实体要负责确保对其拥有的对象进行清理。
如果一个对象具有指向其他对象的实例变量,则称该对象拥有这些对象。例如,在CarParts类中,car对象拥有其指向的engine和tire对象。
同样,如果一个函数创建了一个对象,则称该函数拥有他创建的这个对象。在CarParts中,main()函数创建了一个新的car对象,因此称main()函数拥有car对象。
当多个实体拥有某个特定对象时,对象的所有权关系就更复杂了,这也是保留计数器值可能对于1的原因。
举例:
Engine *engine=[Engine new];
[car setEngine:engine];
现在哪个实体拥有engine对象?是main()函数还是Car类?哪个实体负责确保当engine对象不再被使用时能够收到release消息?谁都不是
解决办法是让Car类保留engine对象,将engine对象的保留计数器值增加到2.这是因为Car类和main()函数都在使用engine对象。Car类应该在setEngine:方法中保留engine对象,而main()函数应该释放engine对象。然后,当Car类完成其任务时再释放engine对象。

9.1.3 访问方法中的保留和释放
编写内存管理程序的第一种方法---setEngine方法的常见版本可能如下所示:
- (void) setEngine:(Engine *)newEngine
{engine=[newEngine retain];
 //错误代码:不要剽窃,看下面的修正版本
}
可惜的是,这样做还远远不够。例如:
Engine *engine1=[Engine new];//count:1
[car setEngine:engine1];//count:2
[engine1 release];//count:1

Engine *engine2=[Engine new];//count:1
[car setEngine:engine2];//count:2

坏了,engine1对象出问题了:他的保留计数器值仍然为1。main()函数已经释放了对engine1对象的引用,但是Car类一直没有释放

engine1对象。现在engine1对象已经发生了泄漏。
下面是编写setEngine:方法的另一种写法:

- (void) setEngine:(Engine *)newEngine
{ [engine release];
  engine=[newEngine retain];

  //更多的错误代码:期待下面的修正版本
}

该例子也有问题,当newEngine和原来的engine对象是同一个对象时,这段代码也会出问题。如:
Engine *engine=[Engine new]; //count:1
Car *car1=[Car new];
Car *car2=[Car new];

[car1 setEngine:engine];//count:2
[engine release];//count:1

[car2 setEngine:[car1 engine]];//oops!

为什么会出现这样的问题?让我们看一下。[car1 Engine]返回一个指向engine对象的指针,该对象的保留计数器值为1.setEngine

方法的第一行是[engine release],该语句将engine对象的保留计数器值归0,并释放engine对象。现在newEngine和engine这两个

实例变量都指向刚释放的内存去,这会引起错误。下面是编写setEngine的一种更好的方法:

- (void) setEngine:(Engine *)newEngine
{
 [newEngine retain];
 [engine release];
 engine=newEngine;

}


9.2 自动释放
内存管理是一个棘手的问题,前面我们知道了在编写setter方法时遇到了一些细微的问题。接下来该解决另一个难题了。很多时候,弄清什么时候不再使用一个对象并不容易。考虑下面这个description方法。

- (NSString *)description
{ NSString *description;
  description=[[NSString alloc] initWithFormat:@"I am %d years old",4];
 return (description);
}

在这个例子中,我们使用alloc方法创建一个新的字符串实例(alloc方法将该对象的保留计数器值设置为1),然后返回该字符串实例。请考虑一下,哪个实体负责清理该字符串对象呢?
怎么样?很棘手吧!实际上只需要更改一行代码改为如下所示的3行就可以:
NSString *desc=[someObject description];
NSLog(@"%@",desc];
[desc release];
这就是更好的办法!

9.2.1 所有对象全部入池
Cocoa中有一个“自动释放池”(autorelease Pool)的概念。他是一个存放实体的池(集合),这些实体可能是对象,能够被自动释放。
NSObject类提供了一个autorelease方法:
- (id) autorelease;
该方法预先设定了一条在将来某个时间发送的release消息,其返回值是接收消息的对象。retain消息采用了相同的技术,是嵌套调用更加容易。当给一个对象发送autorelease消息时,实际上是将该对象添加到NSAutoreleasePool中。当自动释放池被销毁时,会向该池中的所有对象方式release消息。

说明:自动释放的概念并不神秘。你可以使用NSMutableArray来编写自己的自动释放池,以容纳对象并在dealloc方法中向池中的所有对象方式release消息,但是无需另起炉灶编写自动释放池---苹果公司已经替你完成了这项艰巨的任务。
因此,现在可以编写一个能够很好地管理内存的description方法:

- (NSString *)description
{ NSString *description;
  description=[[NSString alloc] initWithFormat:@"I am %d years old",4];

  return ([description autorelease]);
}

9.2.2 自动释放池的销毁时间
NSAutoreleasePool *pool;
pool=[[NSAutoreleasePool alloc] init];
...
[pool release];
创建一个自动释放池时,该池自动成为活动的池。释放该池时,其保留计数器值归0,然后该池被销毁。同时销毁其所包含的对象。
当使用AppKit时,Cocoa定期自动为你创建和销毁自动释放池。通常是在程序处理完当前事件(如鼠标单击或按键)以后执行这些操作。你可以使用任意多的自动释放对象,当不再使用他们时,自动释放池将自动为你清理这些对象。
说明:你可以已经遇见过另一种销毁自动释放池中对象的方式:-drain方法。该方法只是清空自动释放池而不销毁他。-drain方法只适用于Mac OS X 10.4(Tiger)及更高版本。在我们自己编写的代码中,我们使用-release方法,因为该方法适用于Mac OS的所有版本。

9.2.3 自动释放池的工作过程
下面的程序展示了自动释放池的工作过程

int main(int argc,const char *argv[])
{
 NSAutoreleasePool *pool;
 pool=[[NSAutoreleasePool alloc] init];

 RetainTracker *tracker;
 tracker=[RetainTracker new]; //count:1

 [tracker retain];//count:2
 [tracker autorelease];//count:still 2,只是把tracker对象放入了pool中
 [tracker release]; //count:1

 NSLog(@"releasing pool");
 [pool release];
 //gets nuked,sends release to tracker

 return (0);
}

自动释放池被释放之前肯定要先给其中的对象发送release消息。而且如果其中的对象的retainCount>1,对象也不会被销毁,而只是计数值-1.


9.3 Cocoa内存管理规则
Cocoa有许多内存管理的约定,他们都是一些简单的规则。可一致地应用于整个工具包。
说明:与将这些规则复杂化一样,忽略这些规则也是一种常犯的错误。如果你发现自己正在漫无目的地滥用retain和release方法以修正某些错误,那么说明你还没有真正掌握这些规则。这意味着你需要放慢速度,休息一下,吃点零食,然后再继续阅读。
这些规则如下:
1)当你使用new、alloc或copy方法创建一个对象时,该对象的保留计数器值为1。当不再使用该对象时,你要负责向该对象发送一条release或autorelease消息。
2)当你通过任何其他方法获得一个对象时,则假设该对象的保留计数器值为1,而且已经被设置为自动释放,你不需要执行任何操作来确保该对象被清理。如果你打算在一段时间内拥有该对象,则需要保留他并确保在操作完成时释放它。
3)如果你保留了某个对象,你需要(最终)释放或者自动释放该对象。必须保持retain方法和release方法的使用次数相等。

9.3.1 临时对象
对临时对象,如果对象是通过new、alloc或copy方法获得的,需要安排对象的死亡
NSMutableArray *array;
array=[[NSMutableArray alloc] init];//count:1
...
[array release];//count:0

如果使用其他方法获得一个对象,则不需要关心如何销毁该对象
NSMutableArray *array;
array=[NSMutableArray arrayWithCapacity:17];//count:1 ,autoreleased
...

使用NSColor类对象的部分代码:
NSColor *color;
color=[NSColor blueColor];
...

上面的2个例子中的对象都不是通过new、alloc、copy这3个方法获得的,所以可以不用考虑对象的销毁。

9.3.2 拥有对象
对拥有对象,如果对象是通过new、alloc或copy方法获得的,则不需要执行任何其他操作。该对象的保留计算器值一直为1,因此他将一直被保留。只是一定要在拥有该对象的dealloc方法中释放该对象。
例如:
- (void) doStuff
{ flonkArray=[NSMutableArray new];//count:1
}

- (void) dealloc
{
 [flonkArray release];//count:0
 [super dealloc];
}

如果是通过其他获得的对象,你需要保留该对象,考虑编写GUI应用程序时事件循环的情况。你希望保留自动释放的对象,使这些对象在当前的事件循环结束以后仍能继续存在。
那么,什么是事件循环呢?一个典型的图形应用程序往往花费很多时间等待用户操作。在控制程序运行的人非常缓慢地作出决定之前,程序将一直处于空闲状态。当发生这样的事件时,程序被唤醒并开始工作,执行某些必要的操作以响应这一事件。在处理完这一事件之后,程序返回到空闲状态并等待下一个事件的发生。为了降低程序的内存空间占用,Cocoa在程序开始处理事件之前创建一个自动释放池,并在事件处理结束后销毁自动释放池。这样可以使累积的临时对象的数量保持在最低程度。
当使用自动释放对象时,前面的方法可以按如下形式重写:
- (void) doStuff
{ flonkArray=[NSMutableArray arrayWithCapacity:17];//count:1 autoreleased
  [flonkArray retain];//count:2,1 autorelease
}

- (void) dealloc
{[flonkArray release]; //count:0
 [super dealloc];
}

这样防止对象被意外销毁。
请记住,自动释放池被清理的时间是完全确定的:要么是在你自己的代码中显式地销毁,要么是在事件循环结束时使用AppKit销毁。你不必关心守护程序如何随机地销毁自动释放池。因为在调用函数的过程中自动释放池不会被销毁,所以你也不必保留使用的每个对象。

小知识:清理自动释放池 有时,自动释放池未能按照通常预期的情况进行清理。这里有一个来自Cocoa邮件列表的常见问题:“虽然我已经自动释放了我使用的所有对象,但是我的程序占用的内存一直保持绝对的增长”下面的代码通常会引起这样的问题:
int i;
for (i=0;i<1000000;i++) {
 id object=[someArray objectAtIndex:i];
 NSString *desc=[object description];
 //and do something with the description
}
该程序执行一个循环,在大量的迭代中每次都会生成一个(或2个,10个)自动释放对象。请记住,自动释放池的销毁时间是完全确定的,他在循环执行过程中是不会被销毁的,这个循环创建了100万个description字符串对象,所有这些对象都被放进当前的自动释放池中,因此就产生了100万个闲置的字符串。这100万个字符串对象一直存在,只有当自动释放池被销毁时才最终获得释放。
解决这类问题的方法是在循环中创建自己的自动释放池。这样一来,循环每执行1000次,就销毁当前的自动释放池并创建一个新的自动释放池。代码如下:

NSAutoreleasePool *pool;
pool=[[NSAutoreleasePool alloc] init];
int i;
for (i=0;i<1000000;i++) {
 id object=[someArray objectAtIndex:i];
 NSString *desc=[object description];
 //and do something with the description
 if (i % 1000 ==0) {
  [pool release];
  pool=[[NSAutoreleasePool alloc] init];
 }
}
[pool release]

自动释放池以栈的形式实现:当你创建一个新的自动释放池时,他将被添加到栈顶,接收autorelease消息的对象将被放入最顶端的自动释放池中。如果将一个对象放入一个自动释放池中,然后创建一个新的自动释放池再销毁该新建的自动释放池,则这个自动释放对象仍将存在,因为容纳该对象的自动释放池仍然存在。

9.3.3 垃圾回收
Obj-C 2.0引入了自动内存管理机制,也称为垃圾回收。启用垃圾回收功能非常简单,只不过这是一种可选择启用的功能。只需转到项目信息窗口的Build选项卡,然后选择Required [-fobjc-gc-only]选项即可。“-fobjc-gc”选项是为了使代码既支持垃圾回收又支持对象的保留和释放,例如两种环境都使用的库代码。
启用垃圾回收以后,通常的内存管理命令全部变成了空操作指令,不执行任何操作。
Obj-C的垃圾回收器是一种继承性的垃圾回收器。他定期检查变量和对象以及他们之间的指针,当发现没有任何变量指向某个对象时,就将该对象视为应该被丢弃的垃圾。应该注意的是,如果你在一个实例变量中指向某个对象,一定要在某个时候将该实例变量赋值为nil,以取消对该对象的引用并使垃圾回收器知道该对象可以被清理了。
也自动释放池一样,垃圾回收器也是在事件循环结束时触发的。当然,如果不是编写GUI程序,你也可以自己触发垃圾回收器,不过这超出了本书的范围。
有了垃圾回收,你就不必再过多地担心内存管理问题。当使用malloc函数(或利用Core Foundation库中的对象)回收的内存时,还有一些细微的差别,但是我们不讨论这些过于晦涩难懂的内容了。现在,你可以只考虑对象的创建而无需关心他们的释放问题。
请注意:如果开发iPhone软件,则不能使用垃圾回收。实际上,在编写iPhone程序时,苹果公司建议你不要在自己的代码中使用autorelease方法,同时还要避免使用创建自动释放对象的便利函数。

9.4 小结
Cocoa的内存管理办法:retain、release和autorelease。
每个对象都有一个保留计数器,被创建时置为1,retain+1,release-1,retainCount=0时,对象被销毁,指向dealloc方法。
autorelease是把对象放进NSAutoreleasePool中,当pool被销毁时,向其包含的对象发送release消息。
Cocoa内存管理原则:
1)使用new、alloc或copy操作获得一个对象,其retainCount=1.
2)通过其他方法获得一个对象,则假设该对象的retainCount=1,而且已经被设置为自动释放。
3)如果保留了某个对象,则必须保持retain方法和release方法的使用次数相等。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值