要谈oc的内存管理,首先得从对象的生命周期说起。
1.对象的生命周期
1.1引用计数
oc利用引用计数来计算对象的使用寿命。每个对象都有一个与之相联的整数,称作它的引用计数器/保留计数器。
当某段代码需要访问一个对象时,该代码将该对象的保留计数器的值加1,表示为“我要访问该对象”。当这段代码结束对象访问时,将对象的保留计数器值减1,表示它不再访问该对象。当保留计数器值为0,表示不再有代码访问该对象了。因此对象将被销毁,释放内存被系统回收以便重用。
当使用alloc,new方法或者通过copy消息(生成对象的一个副本)创建一个对象时,对象的保留计数器值被置为1。要增加对象的保留计数器值,可以给对象发送retain消息。要减少则发release消息。
当一个对象因其保留计数器归0而即将被销毁时,oc自动向对象发送dealloc消息。你可以重写对象的dealloc方法,释放已经分配的相关资源。发送retainCount来查询引用计数。
下面举个栗子来更深入了解:
@interface RetainTracker : NSObject
@end
@implementation RetainTracker
- (id) init
{
if(self = [super init]){
NSLog(@"init:retainCount :%d",[self retainCount]);
}
return self;
}
- (void)dealloc
{
NSLog(@"dealloc:bye");
[super dealloc];
}
@end
int main (int argc, const char * argv[])
{
RetainTracker *tracker = [RetainTracker new]; //init:retainCount: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 retain]; //count:2
NSLog(@"%d",[tracker retainCount]);
[tracker release]; //count:1
NSLog(@"%d",[tracker retainCount]);
[tracker release]; //dealloc:bye
return 0;
}
1.2对象所有权
也许你觉得内存管理so easy。 但是我告诉你,当你开始思考对象所有权时,内存管理就变得复杂多了。
当我们说某个实体“拥有一个对象”时,就意味着该实体要负责确保对其拥有的对象进行清理。
如果一个对象具有指向其他对象的实例变量,则称该对象拥有这些对象。
举个栗子:实例对象拥有他的成员变量。同样,如果一个函数创建了一个对象,则该函数拥有它创建的这个对象。
当多个实体拥有某个特定对象时,对象的所有权关系就复杂了,这也是保留计数器值可能大于1的原因。在上面的代码例子里,main()函数拥有RetainTracker的对象,因此main()要负责清理该类的对象。
假如有Car类,其engine setter方法为:
-(void)setEngine:(Engine *)newEngine;
在main()里调用该方法:
Engine *engine = [Engine new];
[car setEngine : engine];
现在是哪个实体拥有engine对象呢?是main()函数 还是car类?哪个实体负责确保当engine对象不再被使用时能够收到release消息?因为car类正在使用engine对象,所以不可能是main函数,因为main函数随后可能还会使用engine对象,所以也不可能是car类。
解决办法是让car类保留engine对象,将engine对象的保留计数器增加到2,car类应该在setEngine方法中保留engine对象,而main函数运行结束时释放engine对象,当car类完成其任务时再释放engine对象(在其dealloc方法中),最后engine对象占用的资源被回收。
- (void) setEngine: (Enigne *) newEngine{
if(engine != newEngine)
{
[engine release];
engine = [newEngine retain];
}
}//setEngine
最完美的解决方法是在访问方法中 先判断新旧对象是否相等,如果不是, 先释放旧对象,再保持新对象。
2.自动释放自动释放池(autorelease pool),它是一个存放实体的池(集合),这些实体可能是对象,能够被自动释放。NSObject类提供了一个autorelease方法。
该方法预先设定了一条在将来某个时间发送的release消息,其返回值是接收消息的对象。当给一个对象发送autorelease消息时,实际上是将该对象添加到NSAutoreleasePool中。当自动释放池被销毁时,会向该池中的所有对象发送release消息。
- (NSString *) description
{
NSString *description;
description = [[NSString alloc]
initWithFormat: @"I am %d years old", 4];
return ([description autorelease]);7} // description
自动释放池的销毁时间:
在使用Foundation库工具中,创建和销毁自动释放池的方法非常明确:
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
…
[pool release];
创建一个自动释放池时,该池自动成为活动的池。
释放该池时,其保留计数器值归0,然后该池被销毁。
在销毁过程中,该池释放其包含的所有对象。
当使用AppKit时,Cocoa定期自动为你创建和销毁自动释放池。通常是在程序处理完当前事件(如鼠标单击或按键)以后执行这些操作。
你可以使用任意多的自动释放对象,当不再使用它们时,自动释放池将自动为你清理这些对象。
@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];
}
@end
int main (int argc, const char * argv[])
{
// 创建一个自动释放池
NSAutoreleasePool *pool;
pool = [[NSAutoreleasePool alloc] init];
// 创建(new)一个RetainTracket对象
RetainTracker *tracker = [RetainTracker new]; // count:1
NSLog(@"%d",[tracker retainCount]);
// 处于演示目的,retain一个trancker
[tracker retain]; // count:2
NSLog(@"%d",[tracker retainCount]);
// 向对象发送autorelease消息,将该对象添加到自动释放池pool中
[tracker autorelease]; // count: still 2
NSLog(@"%d",[tracker retainCount]);
// release该对象以抵消上面的 retain
[tracker release]; //count: 1
NSLog(@"%d",[tracker retainCount]);
NSLog(@"releaseing pool");
[pool release];
return 0;
}
3.内存管理规则
1.当你使用new,alloc,copy创建一个对象时,该对象的保留计数器值为1.当不再使用该对象,你要负责向该对象发送一条release或autoRelease消息。这样,该对象将在其寿命结束时销毁。
2.当你通过任何方式获得一个对象时,则假设该对象的保留计数器值为1,而且已经被设置为自动释放,你不再需要执行任何操作来确保该对象被清理。如果你打算在一段时间内拥有该对象,则需要保留它并确保在操作完成时释放它。
3.如果你保留了某个对象,你需要(最终)释放或自动释放该对象。必须保持retain方法和release方法的使用次数相等。
4.垃圾回收(ARC)
oc2.0引入了自动内存管理机制,也称垃圾回收。
垃圾回收器会定期检查变量和对象以及他们之间的指针,当发现没有任何变量指向某个对象时,会将该对象视为垃圾。
最糟糕的事情莫过于保留一个指向不再使用的对象的指针。因此,如果你在一个实例变量中指向某个对象,一定要在某个时候将该实例变量置为nil,以取消对该对象的引用,并使垃圾回收器知道该对象可以被清理。