Setters
如果不对 Objective-C 的内存管理机制有深刻的理解,是很难写出争取的 setter 的。假设一个类有一个名为 title 的NSString类型的属性,我们希望通过 setter 设置其值。这个例子虽然简单,但已经表现出 setter 所带来的主要问题:参数如何使用?不同于 C++,在 Objective-C 中,对象只能用指针引用,因此 setter 虽然只有一种原型,但是却可 以有很多种实现:可以直接指定,可以使用retain指定,或者使用copy。每一种实现都有特定的目的,需要考虑你 set 新的值之后,新值和旧值之间的关系(是否相互影响等)。另外,每一种实现都要求及时释放旧的资源,以避免内存泄露。
直接指定(不完整的代码)
外面传进来的对象仅仅使用引用,不带有retain。如果外部对象改变了,当前类也会知道。也就是说,如果外部对象被释放掉,而当前类在使用时没有检查是否为nil,那么当前类就会持有一个非法引用。
-(void) setString:(NSString*)newString
{
... 稍后解释内存方面的细节
self->string = newString; // 直接指定
} 使用retain指定(不完整的代码)
外部对象被引用,并且使用retain将其引用计数器加 1。外部对象的改变对于当前类也是可见的,不过,外部对象不能被释放,因为当前类始终持有一个引用。
-(void) setString:(NSString*)newString
{
... 稍后解释内存方面的细节
self->string = [newString retain]; // 使用 retain 指定
} 复制(不完整的代码)
外部对象实际没有被引用,使用的是其克隆。此时,外部对象的改变对于当前类是不可变的。也就是说,当前类持有的是这个对象的克隆, 这个对象的生命周期不会比持有者更长。
-(void) setString:(NSString*)newString
{
... 稍后解释内存方面的细节
self->string = [newString copy]; // 克隆
// 使用 NSCopying 协议
} 为了补充完整这些代码,我们需要考虑这个对象在前一时刻的状态:每一种情形下,setter 都需要释放掉旧的资源,然后建立新的。这些代码看起来比较麻烦。
直接指定( 完整代码)
这是最简单的情况。旧的引用实际上被替换成了新的。
-(void) setString:(NSString*)newString
{
// 没有强链接,旧值被改变了
self->string = newString; // 直接指定
} 使用retain指定(完整代码)
在这种情况下,旧值需要被释放,除非旧值和新值是一样的。
// ------ 不正确的实现 ------
-(void) setString:(NSString*)newString
{
self->string = [newString retain];
// 错误!内存泄露,没有引用指向旧的“string”,因此再也无法释放
}
-(void) setString:(NSString*)newString
{
[self->string release];
self->string = [newString retain];
// 错误!如果 newString == string(这是可能的),
// newString 引用是 1,那么在 [self->string release] 之后
// 使用 newString 就是非法的,因为此时对象已经被释放
}
-(void) setString:(NSString*)newString
{
if (self->string != newString)
[self->string release]; // 正确:给 nil 发送 release 是安全的
self->string = [newString retain]; // 错误!应该在 if 里面
// 因为如果 string == newString,
// 计数器不会被增加
}
// ------ 正确的实现 ------
// 最佳实践:C++ 程序员一般都会“改变前检查”
-(void) setString:(NSString*)newString
{
// 仅在必要时修改
if (self->string != newString) {
[self->string release]; // 释放旧的
self->string = [newString retain]; // retain 新的
}
}
// 最佳实践:自动释放旧值
-(void) setString:(NSString*)newString
{
[self->string autorelease]; // 即使 string == newString 也没有关系,
// 因为 release 是被推迟的
self->string = [newString retain];
//... 因此这个 retain 要在 release 之前发生
}
// 最佳实践:先 retain 在 release
-(void) setString:(NSString*)newString
{
[self->newString retain]; // 引用计数器加 1(除了 nil)
[self->string release]; // release 时不会是 0
self->string = newString; // 这里就不应该再加 retain 了
} 复制(完整代码)
无论是典型的误用还是正确的解决方案,都和前面使用retain指定一样,只不过把retain换成copy。
伪克隆
有些克隆是伪克隆,不过对结果没有影响。
3 评论
[self->newString retain];
self->newString 指向哪个?
原文是 [self->newString retain];,不过我觉得或许是笔误?应该是 [newString retain]; 更合理一些。
确实是错误,应该是[newString retain];