在init和dealloc方法中使用访问器的正确方式是什么?

24

我从多个渠道(包括stackoverflow.com、cocoa-dev、文档、博客等)了解到,在init和dealloc方法中使用访问器和设置器(foo,setFoo:)是“错误”的行为。我理解如果这样做会有可能会给观察该属性的其他对象带来困惑,这里提供了一个简单的例子(在这里)

然而,出于以下原因,我不同意这种做法:

新的Objective-C运行时(适用于iPhone和10.5中的64位运行时)允许您声明属性而无需声明相应的实例变量。例如,下面的类将在10.5或iPhone上编译通过(设备,非模拟器):

@interface Foo : NSObject { }

  @property (retain) id someObject;

@end

@implementation Foo

  @synthesize someObject;

@end

理解上述代码是一个完全有效的Objective-C类,假设我要编写一个初始化方法和一个dealloc方法(因为iPhone上没有垃圾回收),以进行内存管理。所有我读过的与初始化方法和释放内存相关的资料都会引导我编写以下两个方法:

- (id) init {
  if (self = [super init]) {
    //initialize the value of someObject to nil
    [self setSomeObject:nil];
  }
  return self;
}

- (void) dealloc {
  //setting someObject to nil will release the previous value
  [self setSomeObject:nil];
  [super dealloc];
}

然而,根据文档和普遍意见,这样做是“错误”的。所以我的问题是:

  1. 如果不能使用访问器,我应该如何初始化someObject?您可能会说编译器(或运行时或其他什么东西)会确保someObject已设置为nil,但我认为依赖于这一点是不恰当的行为。作为一个对C有相当背景的人,我看到过很多因为没有正确初始化变量而导致的错误,这似乎与它们很不一样。
  2. 如果在dealloc方法中不应该使用访问器,我该如何释放someObject?

如果答案是“你不能”,那么在init和dealloc方法中使用访问器会有什么问题呢?


合成属性存储在Leopard及更高版本的64位应用程序中可用。这不是10.6的特性。 - Jim Puls
@Jim谢谢,我忘记了那一点。我已经编辑了问题。 - Dave DeLong
2个回答

9
编辑(2013年2月13日):如下面我的评论中所指出的,特别是自从添加了ARC以来,我的想法已经改变了。在ARC之前,由于init中不正确的ivar赋值导致了很多崩溃性错误。在我看来,特别是与初级团队合作时,在init中使用访问器的罕见问题被ivar访问的常见错误所抵消。由于ARC已经消除了这些类型的错误,使用访问器在init中可能会导致的罕见但可能发生的错误更加重要,因此我已经转而支持在initdealloc中直接使用ivars,并且仅在这些地方使用;其他所有地方都使用访问器(显然你不能在访问器内部使用访问器....)。

ARC之前的答案

我强烈反对那些反对在-init中使用访问器的人。在几乎所有情况下,这是使用访问器的非常好的地方,并且它可以避免我在新的Cocoa编码者中看到的许多错误,他们总是在-init中分配时未能保留。

-dealloc则更难以决定。我自然倾向于在那里使用访问器(这样它们就可以在任何地方使用),但由于KVO(或者甚至是NSNotifications,如果你在你的setter中发布了一个变化通知),这可能会引起头痛。话虽如此,虽然我不在-dealloc中使用访问器,但我认为这是非常值得商榷的,而且苹果公司在这方面非常不一致(我们知道他们在UIViewController的-dealloc中调用了setView:)。

无论如何,我认为过少地使用访问器已经导致了100倍的错误。除非有充分的理由不使用,否则我总是会倾向于使用它们。


1
我认为在init/dealloc中具有平衡的保留/释放的好处非常重要,这也是我反对在init中使用访问器的原因之一。 - Georg Schölly
4
只有在init中对每个需要保留的ivar进行赋值,才能获得这种平衡。通常情况下,在init之后可能会存在未被赋值的ivar(特别是延迟加载对象),这是一种非常重要的性能优化。因此,我认为如果禁止一些重要的实现方法,就不可能在实践中保持这种平衡。 - Rob Napier
6
一年后,我的立场有所软化。在-init中使用访问器可能存在危险。如果一个子类重写了访问器并且超类的-init调用它,该对象可能处于不一致的状态。问题在于,不可能知道你的子类是否会这样做。这是否是真正的可能性取决于你的环境以及是否有不同的组编写超类和子类。制定这个规则极大地减少了我们初级开发人员的崩溃,并且我们从未遇到上述问题。但是有可能存在风险。 - Rob Napier
3
我知道这个帖子已经有一段时间没有更新了,但值得注意的是,即使属性的内存管理语义发生变化,访问器也可以帮助维护代码的有效性。这在dealloc中尤其相关,即使var被分配并且没有保留,self.var = nil;仍然有效,而[var release];则不会。 - Justin Spahr-Summers
1
关于对象的不一致性,我认为一旦分配了对象,它应该始终处于半一致状态。换句话说,如果任何属性都是零初始化,每个对象都应该表现得合理。如果这不成立,那么在init中使用访问器的唯一问题就是构造比C++更加健壮,因为子类可能处于完全未定义的状态。 - Justin Spahr-Summers
1
@justin 和 @rob 得到了很高的分数... 如果我为一个实例变量设置了属性访问器,在 dealloc 中我总是使用 self.thing = nil; 这样如果它被保留了,就会处理掉它,如果它被分配了,也会处理掉它。我非常警惕那些在 dealloc 中盲目调用 ivar 的 release 的人...。 - Greg Combs

8

我知道目前10.5版本的行为是不能直接访问合成的实例变量,苹果公司认为这是一个bug,你应该能够直接访问它,但是现在却不能。

因此,你应该可以这样做:

someObject = nil;

代替
self.someObject = nil;

同时,直接使用访问器是唯一的方法,可以不提供显式 ivar 来完成它。

更新:该 bug 已经修复,现在你可以完全正常地执行 someObject = nil


3
有趣。在 Xcode 中玩了一会儿后,似乎可以直接访问生成的实例变量。很不错。 - Dave DeLong
1
很久以后,有人向我指出文档中说,在合成实例变量的情况下,应该在-dealloc方法中使用setter。抱歉,这意味着我必须将采纳答案授予Rob。http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ObjectiveC/Articles/ocProperties.html - Dave DeLong
不,它并没有说你“应该”使用访问器,而是说因为你无法直接访问实例变量,所以你别无选择。这曾经是事实,但在最近的开发工具版本中,你可以直接访问实例变量。 - BJ Homer

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