Objective-c中正确的变量释放方式

4

我知道在Objective-C中声明变量有一种非常简单的方式,就像这样:

NSArray* myArray;

@property(retain) NSArray* myArray;

@synthesize myArray;

这样你就可以使用self.myArray作为setter和getter,同时保留变量。但是这也允许你做一件更多的事情,就是避免使用dealloc。据我理解,这两行代码是相同的:

self.myArray = nil;
[myArray release];

我的问题是,哪种方法更受欢迎?有没有一种情况下其中一种方法会起作用而另一种方法不起作用?
编辑:抱歉,我是指发布,而不是释放...
4个回答

4

除非在非常特殊的情况下,否则不应该自己调用dealloc。

代替dealloc,你应该调用[myArray release]并让释放过程为您处理。

这里查看有关dealloc方法的更多信息。


抱歉,我本意是要写[myArray release]。但这样做和self.myArray = nil实际上是一样的吗? - Enrico Susatyo
self.myArray = nil 在您的情况下确实会调用 release。但是请记住,self.myArray = nil 与 [self setMyArray:nil] 相同。因此,如果您自己覆盖此方法,请确保做正确的事情。 - slycrel
我尝试使用Instruments运行我的应用程序,虽然使用了self.myArray = nil,但似乎没有任何泄漏。这是因为我已经合成了那个变量的retain属性吗? - Enrico Susatyo
是的,没错 - 具有保留属性的合成属性将在 = 操作中为您保留/释放。 - slycrel

3

更新请看底部。

当使用(保留)合成属性时,最好的dealloc方法是将属性设置为nil。之所以说这是“最好”的方法,是因为它确保了属性声明所暗示的所有协议都得到满足。例如,如果您的属性被声明为原子性(除非您显式地声明为非原子性),仅通过在dealloc中使用属性将其设置为nil,才能保证取消设置该属性时具有相同的原子性保证。这也意味着它会正确地处理对象的任何键值观察 - 这可能特别重要,特别是如果您使用Cocoa绑定。

对于没有相应属性的(可能是私有的)实例变量进行自己的内存管理时,有几种惯用法。最简单但最危险的方法是简单地释放iVar,如下所示:

- (void)dealloc
{
    [myArray release];

    [super dealloc];
}

这将导致iVar上的保留被释放,但正如其他人所提到的,现在可能过时的指针仍然存在,可能会被过时或未保留的指向正在被dealloc的对象的指针访问。接下来沿途的是另一个答案建议的习语:
- (void)dealloc
{
    [myArray release], myArray = nil;

    [super dealloc];
}

如果更加谨慎,可以使用以下习语:

- (void)dealloc
{
    id temp = myArray;
    myArray = nil;
    [temp release];

    [super dealloc];
}

这进一步限制了通过在释放指向的对象之前清除iVar来防止指针过时读取的机会。但是,由于指令重新排序的潜力,即使如此,在所有架构上也不能完全保证不会出现过时读取。
尽管并发和内存管理还有很多要说的,但通常情况下,如果您有一个@synthesized属性setter,则应在dealloc中使用它。这样做意味着,如果更改@property的行为,则dealloc行为将自动与@property声明正确匹配。
重要提示:使用原子属性!=线程安全。(实际上,如果问我,原子属性是浪费,但是…)有关详细信息,请参见here
更新
最近又被投票了,虽然我仍然坚持我在这里关于合成保留属性的原子保证所说的话,而且原始答案中的其他内容本身就具有价值,但我觉得有必要讲述另一面的故事。戴夫·德隆在评论中暗示了其中的一些内容,但我认为将详细信息添加到主要答案中是值得的。
我认为保持原子性保证的唯一方法是通过设置属性的setter将其设置为nil但是你不应该在意,原因如下:如果一个对象正在被dealloc,那么(如果您的对象图正确)就不应该有任何对该对象的活动引用。 如果没有对该对象的活动引用,则不可能有任何人关心清除属性的操作的原子性保证。
我在原始答案中还提到了KVO作为在dealloc中使用setter的原因,但Dave DeLong在评论中提到了KVO作为反驳的理由。 他是对的,原因如下:同样,如果一个对象正在dealloc,则所有KVO观察者应该已从其中删除(再次,不应该有任何活动引用,无论是KVO还是其他)。 实际上,如果不是这种情况,很快就会看到控制台消息,告诉您对象仍然处于观察状态。
简而言之,在dealloc中无法使原子性保证等同于合成的setter,但它永远不会有影响(如果有影响,那么其他问题就出现了)。

那么您的意思是,如果我们在应用程序本身中进行一些多线程编程,那么这些预防措施是必要的?如果我没有进行任何多线程操作,那么只使用[myArray release]就可以了吗? - Enrico Susatyo
现在,你不能百分之百确定 Kit 后台没有多线程操作(几乎总是有的)。一般来说,如果你有一个单线程应用程序,你不必担心它。但是,除非你在 Instruments CPU Sampler/Time Profiler profiles 中看到了 "dealloc > setFoo:",否则进行这种更改就是“过早优化”。而且,坦率地说,如果你确实看到了这种变化,那么你做错了其他事情。 - ipmcc
好的...但是你的回答中是否意味着,如果我在那个变量中有@property (retain),我可以使用self.myVar = nil,它将释放该变量并将其设置为nil?(与您提供的第二个dealloc情况相同) - Enrico Susatyo
是的,没错。使用 '@property (retain) NSObject* foo;' 声明后,设置 'self.foo = nil;' 会导致之前的值被调用 -release 方法并将相应的 iVar 设置为 nil。 - ipmcc
1
dealloc 中将属性设置为 nil文档不建议的。在 dealloc 中进行设置可能会产生意想不到的副作用(子类重写、键值观察等)。http://stackoverflow.com/questions/4580684/common-programming-mistakes-for-objective-c-developers-to-avoid/4580739#4580739 - Dave DeLong
显示剩余2条评论

1

每当您有任何实例变量是一个对象时,您必须在dealloc方法中释放它。所以在您的情况下,您必须使用

- (void)dealloc
{
    [myArray release], myArray = nil;

    [super dealloc];
}

1

在理论上,将变量设置为nil应该与释放ivar具有相同的效果。但是,您永远不应直接调用dealloc方法。在dealloc方法中,有时会看到David提到的习惯用法,具体如下:

[myArray release], myArray = nil;

这样做的原因是为了避免一个非常不可能的竞态条件,即在dealloc完成之前有人尝试访问已释放的对象。通过将属性赋值为nil,可以使尝试访问优雅地失败。

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