新手getter、setter指南

原文地址:http://www.iphonedevsdk.com/forum/iphone-sdk-tutorials/7295-getters-setters-properties-newbie.html

假设有个MyClass类,里面定义了一个成员text。对它的读与写如下所示:

//MyClass.h file
@interface MyClass: NSObject {
	NSString *text;
}

-(void) init;
-(void) logText;
@end

//MyClass.m file
@implementation MyClass 
- (void)init {
	text = @"some text";
}

- (void)logText {
	NSLog(@"%@", text);
}
@end

上面的程序是用常量初始化text,所以不用关心内存管理。

不过,我们肯定是希望在运行时能够改变它,那么需要添加新函数:

//MyClass.h file
@interface MyClass: NSObject {
	...
-(NSString*) text;						// 这就是getter的声明
-(void) setText:(NSString *)textValue;	// 这就是setter的声明
@end

//MyClass.m file
@implementation MyClass
	...
// 这就是getter的定义
-(NSString*) text {
	return text;
}

// 这就是setter的定义
-(void) setText:(NSString *)textValue {
	if (textValue != text)	// 若两个对象不同
	{
		[textValue retain];	// “传入对象”的引用计数加一
		[text release];		// text的引用计数减一(释放堆内存)
		text = textValue;	// 给text赋新值
	}
}

-(void)dealloc {
	[text release];	// 对象的析构函数里要释放text
	[super dealloc];
}

@end

于是,就可以像下面那样读、写text的值:

NSString *theTextValue = [obj text];	// 读
[obj setText:newStringValue];			// 写

因为iOS对NSObject对象的管理是采用引用计数的模式,所以setter要写成上面的样子。当然,也可以写成下面这样:

-(void) setText:(NSString *)textValue {
	[textValue retain];
	[text release];
	text = textValue;
}

去掉了if语句,功能也是正常的,顶多就是对同一对象的引用计数“加一又减一”而已。
但是不能写成下面的样子:
-(void) setText:(NSString *)textValue {
    [text release];		// 先减一
    [textValue retain];	// 再加一
    text = textValue;
}

这是不行的。先减一的话,textValue可能会因为引用计数为零而被释放。对已死的对象再“加一”就不行了。
而且这种bug是隐性的,不会每次都发作。需要程序员用“好习惯”来纠正。

Objective C有一个非常棒的特性:给nil对象发消息是安全的。这就像在C++里回收一个空指针的资源一样安全。

下面给MyClass添加另外一个成员,还有它的getter和setter。

//MyClass.h file
@interface MyClass: NSObject平共处{
	NSString *text;
	int value;
}

-(void) init;
-(void) logText;
-(NSString*) text;
-(void) setText:(NSString *)textValue;
-(int) value;
-(void) setValue:(int*)intValue;
@end

//MyClass.m file
@implementation MyClass

- (void)init {
	text = @"some text";
	value = 2;
}

- (void)logText {
	NSLog(@"%@", text);
}

-(NSString *) text {
	return text;
}

-(void) setText:(NSString *)textValue {
	if (textValue != text)
	{
		[textValue retain];
		[text release];
		text = textValue;
	}
}

-(int) value {
	return value;
}

-(void) setValue:(int)intValue {
	value = intValue;
}

-(void)dealloc {
	[text release];
	[super dealloc];
}

@end

这次添加的是int型的成员,所以无需retain、release。
可以看到,getter和setter是如此的常见,所以Objective C提供了一个捷径:

//MyClass.h file
@interface MyClass: NSObject {
    NSString *text;
    int value;
}

@property(nonatomic, retain) NSString *text;
@property(nonatomic, assign) int value;

-(void) init;
-(void) logText;
@end

//MyClass.m file
@implementation MyClass

@synthesize text;
@synthesize value;

- (void)init {
    text = @"some text";
    value = 2;
}

- (void)logText {
    NSLog(@"%@", text);
}

-(void)dealloc {
    [text release];
    [super dealloc];
}

@end

通过property声明和synthesize定义,Objective C自动生成了getter和setter。而且功能和上面的代码完全一样。
下面讲解一下property里声明的属性。
@property(nonatomic, retain) NSString *text 意为:我有个成员叫text,无需多线程保护,在setter里要使用retain/release过程。
@property(nonatomic, assign) int value 意为:我有个成员叫value,无需多线程保护,在setter里直接赋值就可以,不需要引用计数机制介入。

除了“方框引用法”,Objective C还提供了“点引用法”,来引用成员、成员函数。

NSString *s = [obj text];
[obj setText:@"new string"];
int i = [obj value];
[obj setValue:3];
用点引用法,就可以写成:
NSString *s = obj.text;
obj.text = @"new string";
int i = obj.value;
obj.value = 3;
注意,二者在功能上是完全一样的,都是对getter和setter的调用!用点引用法只是看起来简洁一点而已。

对于Java程序员来说,下面的写法很常见:
void doSomething(String text) {
    this.text = text;
}

如果把Objective C的程序写成上面的样子:

-(void)doSomething:(NSString *)text {
    self.text = text;
}

意义就完全不一样了!它不是对成员的直接引用,而是对setter的直接引用!换言之,是对(void)setText:(NSString *)text的调用。
这有着非常大的不同!其一,setter会有引用计数;其二,调用setter时,有可能该成员还没被初始化。

再看下面的函数,一样具有迷惑性:

-(void)doSomething:(NSString *)input {
	text = input;		// 直接对成员赋值。对NSString对象来说,会有内存泄漏
	self.text = input;	// 会调用setter。保证了成员先释放再赋值,安全
}
所以,使用“self.text =”这种赋值形式是好习惯。
这就解释了,为什么你会在iOS的程序里看到很多下面这样的代码:

-(void)doSomething {
	SomeObject *obj = [[SomeObject alloc] init];
	self.obj = obj;
	[obj release];
}
第一行:分配、初始化后,obj就有了“一个”引用。
第二行:用setter增加引用计数,obj现在有了“两个”引用。
第三行:obj使用结束,引用计数减一,现在是“一个”引用。
所有这些,都可以通过在Xcode的GDB Debugger里用“p [obj retainCount]”来验证。

所以,内存管理的基本原则有两条:
1. 如果你alloc、create、copy了一个对象,最后你要release它;
2. 如果你retain一个对象,最后你要release它。

对象的直接赋值只能出现在一个地方:init。因为它只在alloc之后调用一次:

obj = [[SomeObject alloc] init];
而且,此时所有成员都是nil,直接赋值无妨。

如果在init里不直接赋值会怎么样?会出问题!
self.obj = [[SomeObject alloc] init];

像这样使用了setter的,obj会有两个引用!一个对象刚刚初始化就有了两个引用!它很可能永远不会被释放了。内存泄漏啊。


如果想使用“点引用法”,同时又对编译器产生的setter不满意,怎么办?
以上面的程序为例,如果我们想把text和value联系起来(在修改value的时候,text也跟着变),那么:

-(void) setValue:(int)intValue {
	value = intValue;
	self.text = [NSString stringWithFormat:@"%d", intValue];
}
同时,去掉@synthesize value,换成@dynamic value。这意味着你要提供自己的setter给编译器。


有时,你要创建一个对象给别人使用,那么你就无法选择时机去释放它了。怎么办?

- (MyObject *)getObject {
	return [[MyObject alloc] init];
}
这样是不行的。使用者可能会在使用完后忘记释放它。根据程序界的规律,如果程序员可能忘,那他一定会忘。
解决办法是自动释放:
- (MyObject *)getObject {
	return [[[MyObject alloc] init] autorelease];
}
调用了autorelease方法,系统会在NSAutoreleasePool里注册该对象。这样,“减引用计数”这个责任就落到了“自动释放池”的身上。池负责在合适的时机调用该对象的release方法。


再看上面提到的两条原则:
1. 对象的创建者有责任去释放。反之,不是你创建的,你也不要去释放它。使用者无责任。池会帮你释放。
2. 使用了retain的,仍然有责任去release。因为retain之后,增加了引用。池最后释放的时候,会因为该对象引用数不为零而不释放它。释放的责任在于使用者。

比如前面用到的代码:

-(void) setValue:(int)intValue {
	value = intValue;
	self.text = [NSString stringWithFormat:@"%d", intValue];
}
临时对象NSString的释放就是池负责的。如果该函数的上下文没有池,内存就泄漏了。
PS:原文说每个函数有个隐藏的池,所以退出函数时,这些对象会自动释放。我试验的结果是“绝对不会”。

我想用“点引用法”,又想让对象只读。即不生成setter。怎么办?

@property (nonatomic, readonly) PropertyType propertyName;

若想让对象可读可写,可以用:
@property (nonatomic, readwrite) ...
但这是编译器的默认行为,不写也可以。

编译器还默认对象为“atomic”,即多线程环境下保证安全。不会在多线程同时写,或者一个写一个读的时候发生数据破坏。
除非在你的程序中使用了多线程,否则所有的调用都在一个线程里进行。这样,加上nonatomic可以提高程序的运行效率。

给对象起“别名”也很有用。

@interface MyClass: NSObject {
    NSString *_text;
}
@property(nonatomic, retain) NSString *text;
...
@synthesize text = _text;
这样,类的属性就更名为“text”了。

若想给getter起一个自己喜欢的名字,那么:
@property (nonatomic, assign, getter=isValue) boolean value;

除了retain和assign,还有一个copy操作:
copy只适用于NSObject类,而且实现了NSCopying协议。不能用于int这些基础数据类型。

你可能感兴趣的:(多线程,c,interface,getter,setter,编译器,objective)