为什么在init/dealloc中不应该使用Objective C 2.0访问器?

42
@mmalcthis question的回答中,他指出:“通常情况下,在dealloc(或init)中不应使用访问器方法。”为什么mmalc这样说?
我能想到的唯一原因是性能和避免@dynamic setters的未知副作用。
讨论?

如果楼主没有以“讨论?”结束,这个帖子就不会被关闭。这是一个非常合理和有用的问题——极具建设性。 - Phil
6个回答

29

这基本上是一个减少漏洞可能性的指南。

在这种情况下,你的 setter/getter 可能会无意中对对象的状态做出直接或间接的假设。当对象正在被设置或销毁时,这些假设可能会成为问题。

例如,在下面的代码中,观察者不知道 'Example' 正在被销毁,可能会假设其他已经释放的属性仍然有效。

(你可以认为你的对象在拆除自身之前应该删除所有观察者,这是一个好的实践,也是另一个防止意外问题的指南)。

@implementation Example

-(void) setFoo:(Foo*)foo
{
   _foo = foo;
  [_observer onPropertyChange:self object:foo];
}

-(void) dealloc
{
   ...
   self.foo = nil;
}

@end

2
我明白你的意思,但我并不完全认同。唯一真正的副作用是在对象处于dealloc过程中时触发KVO。这真的很糟糕吗?我已经使用[self setFoo:NULL]样式(在objc2之前)做了一段时间了,但从未遇到过任何问题。 - schwa
2
我希望能够提供一些示例代码来说明问题——如果有的话。有人愿意吗? :-) - schwa
就像我说的那样,这只是一个指南,旨在最小化潜在问题的可能性。就像人们建议您将已释放的指针设置为NULL一样。 - Andrew Grant
3
请参考以下链接:http://lists.apple.com/archives/cocoa-dev/2008/Jul/msg00808.html 或 http://lists.apple.com/archives/Cocoa-dev/2004/Jan/msg01684.html 或 http://developer.apple.com/documentation/Cocoa/Conceptual/CodingGuidelines/Articles/NamingBasics.html#/. - user23743
安德鲁的回答是正确的,事实上,苹果公司的文档中说在 init/dealloc 中不要使用属性:https://developer.apple.com/library/ios/documentation/cocoa/conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW6 - BergQuester
显示剩余4条评论

19

重点在于使用习惯一致的代码。如果您适当地模式化所有代码,则有一组规则可以保证在init / dealloc中使用访问器是安全的。

最大的问题是(正如mmalc所说),设置属性默认状态的代码不应该经过访问器,因为这会导致各种各样的问题。但问题在于,init没有必要设置属性的默认状态。出于多种原因,我一直在转向使用自初始化的访问器,如下面的简单示例:

- (NSMutableDictionary *) myMutableDict {
    if (!myMutableDict) {
        myMutableDict = [[NSMutableDictionary alloc] init];
    }

    return myMutableDict;
}

这种属性初始化方式能够推迟许多可能并非必要的初始化代码。在上述示例中,init方法不需要负责对属性状态进行初始化,因此使用init方法中的访问器完全是安全的(甚至是必要的)。

值得承认的是,这确实对您的代码施加了额外的限制,例如,在超类中具有自定义访问器的子类必须调用超类的访问器,但这些限制与Cocoa中其他常见限制并没有出入。


3
有趣的观点,但请注意(除非您假设进行垃圾回收?)您提供的示例会使myMutableDict被自动释放... - mmalc
7
此外,通过这种方式,您无法将 nil 分配给该属性,因为访问器将自动创建一个新的数组实例。 - Georg Schölly
这是将会施加的额外限制之一,但它并不否定这一点。有许多情况下,您永远不会将nil分配给特定属性,除非在拆卸期间(在这种情况下,这仍然可以正常工作)。其中一个主要例子就是只读属性。 - Louis Gerbarg
另外,既然我在这里,也可以编辑一下,使其对保留/释放更加安全。 - Louis Gerbarg
在我看来,这段代码可能适用于只读属性,而不适用于读写对。一般来说,我认为 object.x = foo 应该意味着紧接着 object.x == fooYES。如果不是的话,也许(非属性)方法会更好。 - Clay Bridges
将初始化本地化到访问器并不是一种完美的做法。在建立复杂的状态关系时,顺序可能很重要。虽然您可以创建单独的初始化方法,这些方法可以由访问器触发,但如果在init中调用初始化,则可能需要添加其他逻辑(可能封装在dispatch_once()中),这是不必要的。我认为这种做法是一个应急措施,忽略了问题的核心--状态依赖性的性质和复杂性需要由代码维护者来协调,而不是通过简单的编码规则来解决。 - ctpenrose

15

你自己回答了这个问题:

  1. 性能可能是一个完全足够的原因(尤其是如果您的访问器是原子的)。
  2. 您应避免访问器可能具有的任何副作用。

后者特别是在您的类可以被子类化时成为一个问题。

然而,不清楚为什么这特别针对于 Objective-C 2 访问器?使用声明属性或自己编写访问器同样适用这些原则。


2
可能是因为setter有应该运行的逻辑,或者实现中使用了与getter/setter名称不同的ivar,或者两个ivar需要被释放和/或其值设置为nil。唯一确定的方法是调用setter。setter的责任是在init或dealloc期间调用时不发生不良副作用。
从“Cocoa Design Patterns”(Buck,Yacktman,第115页):“……当您使用现代Objective-C运行时或......时,没有实际替代使用访问器”。

在我的这个问题(https://dev59.com/_3M_5IYBdhLWcg3wp0-l)中,一个回答揭示了即使只是声明属性(一个合成的ivar),你仍然可以直接访问ivar。 - Dave DeLong
@Dave 直接访问 ivar 假定您知道它的名称,并且只有一个 ivar 会被 setter 更改。但是,人们不应该知道(或关心)setter 的实现方式。 - zaph
Setter/Getter 可以被覆盖。它们可以释放其他资源(例如观察者)。 - Sulthan

0
事实上,对于一个经常出现和消失的类(比如详细视图控制器),你应该在 init 中使用访问器;否则,你可能会在 viewDidUnload 中释放一个稍后要访问的值(他们在 CS193P 中展示了这一点...)。

0

当分配/释放内存时,如果不调用setter方法,就会出现相同的问题。

我认为在init/dealloc中直接使用retain/release并不能实现任何目标。你只是改变了可能出现的错误集合。

每次都必须考虑属性分配/释放的顺序。


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