在viewDidUnload中将其设置为nil,但在dealloc中释放

10
我整天都在阅读关于为什么应该在viewDidUnload中将视图设置为nil并在dealloc中释放的文章。所有的文章都一遍又一遍地重复同样的事情。是的,我知道背后的指令不同,但实际上有什么区别呢? var = nil
  1. 如果var是一个被保留的属性,则回收旧对象var指向的内存。
  2. 将var设置为nil。
[var release]
  1. 回收var指向的内存。
  2. var现在指向空,相当于nil
对我来说,这两种回收内存的方式具有相同的最终结果。那么为什么要选择其中一种?每一本书都告诉我在viewDidUnload中设置为nil,在dealloc中释放。如果一个视图在viewDidUnload中被释放并在dealloc中被设置为nil,会发生什么坏事,有人应该指出来。
#import <UIKit/UIKit.h>
@interface DisclosureDetailController : UIViewController {
 UILabel* label;
}
@property (nonatomic, retain) IBOutlet UILabel* label;
@end

.m

#import "DisclosureDetailController.h"
@implementation DisclosureDetailController
@synthesize label;
- (void)viewDidUnload {
 self.label = nil;
 // OR [self.label release];
 [super viewDidUnload];
}
- (void)dealloc {
 [self.label release];
 // OR self.label = nil;
}
2个回答

25

首先要做的事情是这一行:

[self.label release];

无论你在何处调用,这都是绝对错误的。你应该永不在属性访问的结果上调用-release方法。这与写[[self label] release]完全相同,我希望你能认识到这是错误的。

你的代码示例应该像下面这样:

- (void)viewDidUnload {
    self.label = nil;
    [super viewDidUnload];
}
- (void)dealloc {
    [label release];
    [super dealloc];
}

首先看一下-viewDidUnload方法,它很简单。 self.label = nil; 是正确的写法。类似的写法还有 [self setLabel:nil];。虽然不太好,但也可以接受的写法是 [label release], label = nil;。这种写法并不太好,因为它会绕过setter方法,而setter方法可能会比释放属性(例如,它可能维护着关于属性值的内部状态)做更多的事情。它还会绕过KVO通知。

问题是出现在 -dealloc中该怎么处理。许多人建议使用 self.label = nil;,实际上,这样做大多数情况下都没问题。但问题是,在某些情况下,这样做会导致微妙的错误。调用setter方法可能会引起两个问题。第一个问题是如果setter方法是手动实现的话(即使你自己不实现setter方法,子类也可能实现),它可能会在你的类中引起副作用。第二个问题是它可以广播KVO通知。在 -dealloc 中,这两件事情都是不希望的。通过直接释放成员变量,例如: [label release];,您可以避免潜在的副作用和KVO通知。


3
副作用可以是任何事情。 Setter只是一个方法。编译器会为您合成setter,但您可以自由地通过编写一个名为(默认情况下)-setPropertyName:的方法来实现它们。在此setter中,您可以做任何想做的事情,包括执行不适合在dealloc内部执行的操作,例如保留self。同样,在dealloc内部使用KVO通知是一个坏主意。这一点应该很明显。您的对象正在消失 - 没有人应该得到您拆除ivars的详细信息。 - Lily Ballard
1
@JoJo:我不理解你关于self是一个@property的评论。这个概念没有任何意义。属性属于对象。如果self是一个属性,那它会属于哪个对象呢?实际上,self只是Obj-C编译器插入的方法调用的隐式参数。您可以使用self->ivarName引用实例变量,这在功能上与仅使用ivarName相同。 - Lily Ballard
3
@JoJo:是的,指向已释放对象的指针与指向 nil 的指针非常不同。前者是指向垃圾内存的指针,如果尝试对其执行任何操作(例如调用“-release”),几乎肯定会导致程序崩溃。而指向 nil 的指针则是安全的。它将忽略发送给它的任何消息,例如说“id foo = nil; [foo doSomething];”会完全跳过对“-doSomething”方法的调用。如果在“-viewDidUnload”中释放了对象但未将其置为nil,则在“-dealloc”中再次释放该对象或使用“self.foo = somethingElse”时,程序将会出现崩溃。 - Lily Ballard
1
@JoJo:一般情况下,在-init、-dealloc和自定义getter/setter之外的任何地方都使用self.label。在这些情况下,使用labelself->label结构只有在-copyWithZone:内部或某些情况下(通常在此处将使用属性)才真正有用,并且在其他地方被认为是不好的形式。 - Lily Ballard
2
@JoJo:点号“.”不会被转换为箭头“->”,这没有任何意义。人们使用“[self.property method]”是因为通常情况下,如果存在属性,则更喜欢属性访问而非直接访问实例变量。通过尽可能地坚持属性访问,您可以更轻松地遵守属性强制执行的内存管理语义,如果该属性被标记为原子访问,则可以获得原子访问,并且您的代码更加灵活,因为getter/setter可以被子类或者在类的开发过程中被您自己重写。 - Lily Ballard
显示剩余13条评论

4

实际的区别如下。

通过使用属性访问器将属性设置为nil,将使合成方法在释放现有属性后掌握您的新nil属性。

// we will take for granted that you synthesize this property
@property (nonatomic, retain) IBOutlet UILabel* label;

我们将使用属性访问器并将其设置为nil。
//This will in actuality set the new value of nil to the label variable after
//releasing the existing label that it had a retain count on.
self.label = nil; 

接下来我们将直接发布它。
//This line on the other hand will merely release the label directly. 
//As soon as the label is deallocated you will have a handle to an invalid object. 
//(memory space that used to be your label)
[label release];

现在我们将展示属性访问器的简化版本。(仅供参考,不可直接使用)
//Simply put the following is an pseudo equivalent of the property setter.
[label release]
label = nil;

重点在于属性访问器会处理释放它所保留的标签,然后将其设置为你传递的值(在本例中为nil)。

因此,添加以下代码:

label = nil;

如果不释放保留的对象,会导致内存泄漏,并且您将对一个标签保留计数,而您不再拥有指针。

注意:

还有一件事情需要考虑。任何空指针都可以接受消息。而它们将以nil作为回复。另一方面,已释放的对象,一旦该内存被释放,您对其发送的消息很可能会引发错误。结果是不可预测的。这是将属性设置为nil的好理由。它不仅会处理它所持有的对象的释放,而且还会给您一个可以安全地发送消息而不会崩溃的对象。

@WaltSellers的一个好观点

在完全释放变量之后访问变量(无论是属性访问器还是实例变量)将导致“未定义”的操作。这意味着访问可能正常运行,或者可能破坏应用程序的其他部分,或者可能会迅速崩溃并终止有问题的应用程序。基本上,在释放后将变量设置为nil将使您能够通过该错误。

我提供的另一个提示

为了克服属性访问器和实例变量的误解,我只是@synthesize并告诉它设置一个变量名。

@synthesize label = _label;

这样做可以区分self.label和它的实例变量。现在,您不能直接访问label变量,而是需要加上前缀_。

你在关于[label release]的评论中混淆了“属性”、“ivar”和“对象”。 - Lily Ballard
1
有时我会交替使用这些术语。我承认这是一个不好的习惯。将来我会努力更好地展示它们之间的区别。感谢您的批评。 - The Lazy Coder
上述注释的建议:请记住,当一个对象被释放时,内存会被释放,但是内存地址仍然存在于您的成员变量中。现在它是一个悬空指针。通过将悬空指针设置为nil,您可以防止相关的异常和崩溃。 - Walt Sellers
好的,我把你的评论加入了帖子。我有一段时间没有来过这篇文章了。所以在此期间,我对帖子进行了扩展。 - The Lazy Coder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接