存取方法
本章向您介绍为什么应该使用存取方法,以及您应该如何声明和实现它们。
使用存取方法的一个主要原因是封装(见Objective-C面向对象编程中的“封装”部分)。在引用计数环境中,使用存取方法还有一个特别的好处,它们可以为您的类处理大部分的基本内存管理。
通常,您应该使用Objective - C的属性声明功能来声明存取方法,例如:
@property (copy) NSString *firstName; |
@property (readonly) NSString *fullName; |
@property (retain) NSDate *birthday; |
@property NSInteger luckyNumber; |
上述声明为属性指定了明确的内存管理语义。
在很多情况下,您可以(而且应该)避免实现您自己的存取方法,而使用Objective-C的属性声明功能,并要求编译器为您合成存取方法:
@synthesize firstName; |
@synthesize fullName; |
@synthesize birthday; |
@synthesize luckyNumber; |
即使您需要提供您自己的实现,您也应该使用声明属性来声明存取方法—当然,您必须保证您的实现符合您给出的规范。(要特别注意的是,在默认情况下,声明属性是原子的;如果您不提供原子的实现,那么您应该在声明中指定nonatomic
。)
对于简单的对象值来说,大致有三种方式来实现存取方法:
1. Getter在返回值之前保留并自动释放该值;setter释放旧的值并保留(或复制)新的值。
2. Getter返回值;setter自动释放旧的值并保留(或复制)新的值。
3. Getter返回值;setter释放旧的值并保留(或复制)新的值。
方法 1
在方法1中,getter返回的值在调用作用域内被自动释放:
- (NSString*) title { |
return [[title retain] autorelease]; |
} |
|
- (void) setTitle: (NSString*) newTitle { |
if (title != newTitle) { |
[title release]; |
title = [newTitle retain]; // Or copy, depending on your needs. |
} |
} |
由于get存取器返回的对象在当前作用域内被自动释放,因此,如果属性的值发生改变,它仍然有效。这使得存取器更加健壮,但额外开销更多。如果您希望您的getter方法能够被频繁调用,则您也应该考虑到,相比于这种做法带来的性能开销,保留和自动释放对象所增加的额外开销是很不值得的。
方法 2
和方法1一样,方法2也使用了自动释放技术,但这次是在setter方法中完成:
- (NSString*) title { |
return title; |
} |
|
- (void) setTitle: (NSString*) newTitle { |
[title autorelease]; |
title = [newTitle retain]; // Or copy, depending on your needs. |
} |
在getter比setter调用更频繁的情况下,方法2的性能明显好于方法1。
方法 3
方法3完全没有使用自动释放技术:
- (NSString*) title { |
return title; |
} |
|
- (void) setTitle: (NSString*) newTitle { |
if (newTitle != title) { |
[title release]; |
title = [newTitle retain]; // Or copy, depending on your needs. |
} |
} |
方法3所采用的方式非常适合于getter和setter方法被频繁调用的情况。它也非常适合于那些不希望延长值的生命周期的对象,比如集合类。它的缺点是,旧的值可能被立即回收(如果没有其他的所有者),如果另一个对象持有指向该值的非所有性引用,那么这将导致一个问题。例如:
NSString *oldTitle = [anObject title]; |
[anObject setTitle:@"New Title"]; |
NSLog(@"Old title was: %@", oldTitle); |
如果anObject
是拥有原始标题字符串的唯一对象,那么该字符串会在新的标题被设置之后被回收。随后,日志语句将引起程序崩溃,因为oldTitle
是一个已被释放的对象。
在Objective-C代码中,复制值对象—表示属性的对象—是一种很普遍的做法。C-类型的变量通常可以取代值对象,但值对象具有封装常用操作的优势。例如,NSString
对象被用来代替字符指针,因为它们封装了编码和存储。
当值对象作为方法的参数被传递或者从一个方法被返回时,通常会使用对象的副本而不是对象本身。例如,请仔细思考下面的方法,该方法将一个字符串赋值给对象的name
实例变量。
- (void)setName:(NSString *)aName { |
[name autorelease]; |
name = [aName copy]; |
} |
存储aName
的一个副本,其效果是产生一个独立于原始对象,但与原始对象具有相同内容的对象。副本的后续变化不会影响原始对象,并且原始对象的变化也不会影响副本。类似地,一种常见的做法是返回实例变量的副本,而不是实例变量本身。例如,下面的方法返回name
实例变量的一个副本:
- (NSString *)name { |
return [[name copy] autorelease]; |
} |