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];