内存管理基础
当你为Mac OSX编写应用程序时,你可以选择允许垃圾回收。这意味着如果不是在特别复杂的情况下,你不用考虑内存管理。然而,你并不会总是工作在支持垃圾回收的环境中。这样的话,你就需要知道一些基本概念。如果你通过手工alloc的方式创建一个对象,之后你需要release这个对象。同样,你也不能手工释放(release)一个能自动释放(autoreleased)的对象,因为这将会使你的应用程序崩溃。
以下是两个例子:
//string1 将被自动释放
NSString* string1 = [NSString string];
//必须在用完后手工释放
NSString* string2 = [[NSString alloc] init];
[string2 release];
在这里,你可以认为自动释放对象会在当前函数结束的时候被自动释放。
关于内存管理要学的东西很多,但是我们先了解一下其他的概念,这样我们会有更多的认识。
设计类接口
在Objective-C的语法中,创建一个类是非常简单的。一个类通常分为两部分。类的接口(interface)通常存放在类似ClassName.h的文件中,在这里,我们定义实例变量和公用(public)方法。类的实现存放在ClassName.m这样的文件中,它包含了这些方法的实际实现代码。它通常还定义了客户类不能访问的私有(private)方法。
一个接口文件看上去像以下这样的。这个类名字叫做Photo,所以接口文件名是Photo.h:
#import <Cocoa/Cocoa.h>
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
@end
首先,我们导入了Cocoa.h,目的是将Cocoa应用程序的基本类添加进来。#import指令会自动防止将同一个文件导入多次。@interface表明这是类Photo的声明。冒号后面指定父类(superclass),这里父类是NSObject。在花括号里面声明了两个实例变量:caption和photographer。都是NSString类型,实例变量可以是任何对象类型,包括id类型。最后,@end符号结束类的声明。
添加方法
让我们给实例变量添加一些获取器(getter)
#import <Cocoa/Cocoa.h>
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
caption;
- photographer;
@end
记住,Objective-C语言中通常省略方法的“get”前缀。方法名字前面的单个减号(-)表明该方法是一个实例方法。如果方法名字前面是一个加号(+),则表明该方法是一个类(static)方法。
编译器会默认一个方法的返回值是一个id类型的对象,所有的输入参数也默认是id; 类型。上述代码在技术上是正确的,但是我们一般不这样写,我们需要给这些方法指定返回值类型。
#import <Cocoa/Cocoa.h>
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*)caption;
- (NSString*)photographer;
@end
现在,我们来添加设置器(setter):
#import <Cocoa/Cocoa.h>
@interface Photo : NSObject {
NSString* caption;
NSString* photographer;
}
- (NSString*)caption;
- (NSString*)photographer;
- (void) setCaption: (NSString*)input;
- (void) setPhotographer: (NSString*)input;
@end
设置器不需要有返回值,所以我们指定返回值是void。
类实现
现在,我们从获取器(getter)开始,来创建一个类的实现。
#import "Photo.h"
@implementation Photo
- (NSString*) caption {
return caption;
}
- (NSString*) photographer {
return photographer;
}
@end
这段代码以@implementation和类的名字开始,并且像接口一样,有一个@end。所有的方法必须写在这两条语句之间。如果你写过代码,就会觉得上面的获取器看上去很熟悉,所以我们还是来看一看设置器,它们需要多一点解释。
- (void) setCaption: (NSString*)input
{
[caption autorelease];
caption = [input retain];
}
- (void) setPhotographer: (NSString*)input
{
[photographer autorelease];
photographer = [input retain];
}
每一个设置器都要处理两个变量,第一个是当前引用的对象,第二个是新输入的对象。在带有垃圾回收机制的环境中,我们可以直接设置成新的值。
- (void) setCaption: (NSString*)input
{
caption = input;
}
但是,如果你不能使用垃圾回收,你需要release旧的对象,并且retain新的对象。释放一个对象的引用实际上有两种方法:release 和 autorelease。标准的release会立刻释放对象的引用。autorelease会等一会儿才释放,但是引用实际上会一直存在,直到当前方法结束(除非你添加自定义的代码来明确的改变它)。在设置器里面使用autorelease方法会更加安全一些,因为要改变的变量的新旧两个值可能指向的是同一个对象。而你可能不希望立刻释放实际上你要保留的对象。现在,这看上去有点让人迷惑,但是随着你的不断学习,你就会有更多的认识。所以,现在不必彻底的理解这些。
Init
我们可以创建一个init方法用来给我们的实例变量设置初始化值:
- (id) init
{
if ( self = [super init] )
{
[self setCaption:@"Default Caption"];
[self setPhotographer:@"Default Photographer"];
}
return self;
}
这段代码是完全不需要加以说明的,尽管第二行看上去有点不常见。它是一个单个的等号(=),作用是将[super init]的结果赋值给self。这实际上是要求父类做(父类的)初始化操作。if语句的作用是在尝试设置(本对象的)缺省值之前验证父类是否初始化成功。
Dealloc
dealloc方法在一个对象从内存中删除时被调用。通常在这个方法里面释放所有对象里的实例变量。
‐ (void) dealloc
{
[caption release];
[photographer release];
[superdealloc];
}
在前两行,我们直接调用了实例变量的release方法。在这里,我们不需要使用autorelease,因为标准的release更快一些。最后一行非常重要,我们发送了一个[superdealloc]消息,要求父类做清理工作。如果我们不做的话,该对象就不会被从内存中删除,这就造成了内存泄露。当启用垃圾回收机制时,对象的dealloc方法不会被调用。此时,你可以实现一个finalize方法来代替它。
内存管理
Objective-C的内存管理是基于引用计数的。你要做的事情只是关注你的引用,而释放内存的工作实际上由运行环境完成。在最简单的情形中,你分配的(alloc)对象,或者是保留(retain)在一些地方的对象,都需要给他们发送一个release消息。这也意味着,如果你使用了一次alloc,然后又retain了一次,那么你需要release两次才能释放该对象的内存。
这就是引用计数的理论。在实际应用中,通常只有两个原因我们才会创建一个对象:
1. 作为一个实例变量保留。
2. 在函数内部作为临时变量使用。
大多数情况下,一个实例变量的设置器(setter)会自动释放(autorelease)原来引用的对象,同时保留(retain)新的。你只需要保证在dealloc函数中释放(release)了它就行了。
那么,我们实际要做的工作就只有管理函数内部的本地引用了。在这里只有一条规则:如果过你通过alloc或者copy创建了一个对象,在函数结尾的地方给它发送一个release或者autorelease消息就行了。如果你是通过其它方式创建的对象,就什么也别做。下面是第一个例子,管理实例变量:
- (void) setTotalAmount: (NSNumber*)input
{
[totalAmount autorelease];
totalAmount = [input retain];
}
- (void) dealloc
{
[totalAmount release];
[super dealloc];
}
下面是另外一个例子,关于本地引用。我们只需要释放通过alloc创建的对象就行了:
NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];
NSNumber* value2 = [NSNumber numberWithFloat:14.78];
// only release value1, not value2
[value1 release];
下面是一个组合例子,将一个本地引用设置给实例变量:
NSNumber* value1 = [[NSNumber alloc] initWithFloat:8.75];
[self setTotal:value1];
NSNumber* value2 = [NSNumber numberWithFloat:14.78];
[self setTotal:value2];
[value1 release];
注意,不论你是不是把本地引用当成实例变量一样赋值,管理它们都是完全相同的。你不必考虑设置器(setter)是如何实现的。如果你理解了这些,你就理解了关于Objective-C内存管理中90%你需要知道的内容。