我有时会告诉自己。我们的类也经常这样做。但在 Objective-C 中,有几个地方这样做是有风险的:init
和 dealloc
。
本文是Objective-C 中的代码气味系列文章中的一篇。
在 Objective-C 的 init
和 dealloc
代码中,我经常看到这样的代码。我举一个简单的例子。你能找出问题所在吗?
- (id)initWithFoo:(id)foo
{
self = [super init];
if (self)
self.something = foo;
return self;
}
- (void)dealloc
{
self.something = nil;
[super dealloc];
}
提示:是那些self.
。它们容易让人误以为是简单的作业。但请记住,点符号隐藏着信息。
让我们避开点符号,再试一次:
- (id)initWithFoo:(id)foo
{
self = [super init];
if (self)
[self setSomething:foo];
return self;
}
- (void)dealloc
{
[self setSomething:nil];
[super dealloc];
}
现在你看到了吗?
当给自己的信息有气味的
向自己发送信息通常没有问题。但有两个地方要避免:
- 创建对象时,以及
- 对象被销毁时。
在这两个时间段,物体处于一种有趣的、介于两者之间的状态。它缺乏完整性。在这两个时间段调用方法是一种代码缺陷。为什么呢?因为每个方法在对对象进行操作时都应保持不变。下面是对象在方法中流动时的自我一致性(self-consistency)概述:
- 开始:假设对象是自我一致性(self-consistency)的。
- 进行中:对象状态处于变化中。
- 结束:恢复对象自我一致性(self-consistency)的不变性。
提示:不变性使你保持清醒。
我并没有为此做出非常规的尝试。苹果公司有一份关于实用内存管理的文档,其中有一节的标题是 "不要在初始化方法和 dealloc 中使用访问方法"。
Don’t Use Accessor Methods in Initializer Methods and dealloc
The only places you shouldn’t use accessor methods to set an instance variable are in initializer methods and
dealloc
. To initialize a counter object with a number object representing zero, you might implement aninit
method as follows:- init { self = [super init]; if (self) { _count = [[NSNumber alloc] initWithInteger:0]; } return self; }
To allow a counter to be initialized with a count other than zero, you might implement an initWithCount: method as follows:
- initWithCount:(NSNumber *)startingCount { self = [super init]; if (self) { _count = [startingCount copy]; } return self; }
Since the Counter class has an object instance variable, you must also implement a dealloc method. It should relinquish ownership of any instance variables by sending them a release message, and ultimately it should invoke super’s implementation:
- (void)dealloc { [_count release]; [super dealloc]; }
Objective-C init/dealloc:拯救 ivars
解决方法很简单:在 Objective-C 的 init
和 dealloc
方法中,直接访问实例变量,而不是通过属性。在非 ARC 代码中,检查属性属性是否保留或分配。然后编写与直接访问相匹配的代码。例如,如果某个属性是保留属性,默认支持 ivar _something
,那么我们的代码就会变成:
- (id)initWithFoo:(id)foo
{
self = [super init];
if (self)
_something = [foo retain];
return self;
}
- (void)dealloc
{
[_something release];
[super dealloc];
}
在 init/dealloc 中向 self 发送信息时仍能正常工作
在说过 "避免在 init
和 dealloc
中向 self
发送信息 "之后,我现在想缓和一下这种说法。毕竟有两个地方是可以这样做的:
- 在
init
阶段的最后阶段,以及 - 在 dealloc 开始时
这是因为在这两个地方,对象具有自一致性( self-consistency)。在 init
中,所有 ivars 都已建立。在 dealloc
中,没有一个 ivars 被销毁。
但您仍需谨慎行事,并认识到自己在对象生命周期中的位置。仅仅创建一个对象并不能开始任何繁重的工作。创建和销毁都要轻便快捷。
译自 Jon Reid 的 Objective-C init: Why It’s Helpful to Avoid Messages to self
侵删