从 C++ 到 Objective-C(16):内存管理(续三)

Getters

Objective-C 中,所有对象都是动态分配的,使用指针引用。一般的,getter 仅仅返回指针的值,而不应该复制对象。getter 的名字一般和数据成员的名字相同(这一点不同于 Java,JavaBean 规范要求以 get 开头),这并不会引起任何问题。如果是布尔变量,则使用 is 开头(类似 JavaBean 规范),这样可以让程序更具可读性。

@interface Button
{
    NSString* label;
    BOOL      pressed;
}

-(NSString*) label;
-(void) setLabel:(NSString*)newLabel;
-(BOOL) isPressed;
@end

@implementation Button
-(NSString*) label
{
    return label;
}

-(BOOL) isPressed
{
    return pressed;
}

-(void) setLabel:(NSString*)newLabel {...}
@end

当返回实例数据指针时,外界就可以很轻松地修改其值。这可能是很多 getter 不希望的结果,因为这样一来就破坏了封装性。

@interface Button
{
    NSMutableString* label;
}

-(NSString*) label;
@end

@implementation Button
-(NSString*) label
{
    return label; // 正确,但知道内情的用户可以将其强制转换成 NSMutableString,
                  // 从而改变字符串的值
}

-(NSString*) label
{
    // 解决方案 1 :
    return [NSString stringWithString:label];
    // 正确:实际返回一个新的不可变字符串
    // 解决方案 2 :
    return [[label copy] autorelease];
    // 正确:返回一个不可变克隆,其值是一个 NSString(注意不是 mutableCopy)
}
@end

循环retain

必须谨慎避免出现循环 retain。如果对象 A retain 对象 B,B 和 C 相互 retain,那么 B 和 C 就陷入了循环 retain:

A → B ↔ C

如果 A release B,B 不会真正释放,因为 C 依然持有 B。C 也不能被释放,因为 B 持有 C。因为只有 A 能够引用到 B,所以一旦 A release B,就再也没有对象能够引用这个循环,这样就不可避免的造成内存泄露。这就是为什么在一个树结构中,一般是父节点 retain 子节点,而子节点不 retain 父节点。

垃圾收集器

Objective-C 2.0 实现了一个垃圾收集器。换句话说,你可以将所有内存管理交给垃圾收集器,再也不用关心什么retainrelease之类。但是,不同于 Java,Objective-C 的垃圾收集器是可选的:你可以选择关闭它,从而自己管理对象的生命周期;或者你选择打开,从而减少很多可能有 bug 的代码。垃圾收集器是以一个程序为单位的,因此,打开或者关闭都会影响到整个应用程序。

如果开启垃圾收集器,retainreleaseautorelease都被重定义成什么都不做。因此,在没有垃圾收集器情况下编写的代码可以不做任何改变地移植到有垃圾收集器的环境下,理论上只要重新编译一遍就可以了。“理论上”意思是,很多情况下涉及到资源释放处理的时候还是需要特别谨慎地对待。因此,编写同时满足两种情况的代码是不大容易的,一般开发者都会选择重新编写。下面,我们将逐一解释这两者之间的区别,这些都是需要特别注意的地方。

finalize

在有垃圾收集器的环境下,对象的析构顺序是未定义的,因此使用dealloc就不大适合了。NSObject增加了一个finalize方法,将析构过程分解为两步:资源释放和有效回收。一个好的finalize方法是相当精妙的,需要很好的设计。

weak, strong

很少会见到__weak__strong出现在声明中,但我们需要对它们有一定的了解。

默认情况下,一个指针都会使用__strong属性,表明这是一个强引用。这意味着,只要引用存在,对象就不能被销毁。这是一种所期望的行为:当所有(强)引用都去除时,对象才能被收集和释放。不过,有时我们却希望禁用这种行为:一些集合类不应该增加其元素的引用,因为这会引起对象无法释放。在这种情况下,我们需要使用弱引用(不用担心,内置的集合类就是这么干的),使用__weak关键字。NSHashTable就是一个例子。当被引用的对象消失时,弱引用会自动设置为nil。Cocoa 的 Notification Center 就是这么一个例子,虽然这已经超出纯 Objective-C 的语言范畴。

NSMakeCollectable()

Cocoa 并不是 Mac OS X 唯一的 API。Core Foundation 就是另外一个。它们是兼容的,可以共享数据和对象。但是 Core Foudation 是由纯 C 编写的。或许你会认为,Objective-C 的垃圾收集器不能处理 Core Foundation 的指针。但实际上是可以的。感兴趣的话可以关注一下NSMakeCollectable的文档。

AutoZone

由 Apple 开发的 Objective-C 垃圾收集器叫做 AutoZone。这是一个公开的开源库,我们可以看到起源代码。不过在 Mac OS X 10.6 中,垃圾收集器可能有了一些变化。这里对此不再赘述。

3 Comments

  1. willonboy's blog 2011年4月4日
  2. willonboy's blog 2011年4月4日
    • DevBean 2011年4月4日

Leave a Reply