@property(保留)是不是自动释放或释放对象?

Posted

技术标签:

【中文标题】@property(保留)是不是自动释放或释放对象?【英文标题】:Does @property (retain) autorelease or release objects?@property(保留)是否自动释放或释放对象? 【发布时间】:2011-08-15 02:42:14 【问题描述】:

我在使用下面的超级简单代码时遇到了完全出乎意料的计时问题。其中一个变量是自动释放的,我不知道为什么。我没有使用 autorelease、KVO 等。它不应该发生。

WindowController 设置为 @property (retain)'d of MainController

MainController-dealloc 中,我做self.windowController = nil;

但是,它一直等到自动释放池被刷新以释放 windowController。我希望在 self.windowController = nil 完成后立即调用 WindowController 的 dealloc。即使我将 [mainController release] 包装在 NSAutoreleasePool 中,它仍然不会立即释放。

为什么会这样?


这对于@property / NSWindowController 来说似乎不是正确的行为。我错过了什么吗?


更正:它不是绑定。我正式不知道问题出在哪里。

主驱动:

[[MainController new] release];

MainController.h:

#import <Foundation/Foundation.h>
#import "WindowControllerSubclass.h"
@interface MainController : NSObject 
    WindowControllerSubclass *wc;


@property (retain) WindowControllerSubclass *wc;

@end

MainController.m:

#import "MainController.h"

@implementation MainController

@synthesize wc;

- (id)init 
    if (self = [super init]) 
        // This is problem here >>>  If I assign directly to wc, then it's not added to autorelease pool
        self.wc = [[WindowControllerSubclass alloc] init];
        [self.wc release]; // since it's (retain)'d
    

    return self;


- (void) dealloc 
    self.wc = nil;
    NSLog(@"%@ deallocd (should be called after WC's dealloc)", [self className]);


@end

MainWindowControllerSubclass.h:

#import <Cocoa/Cocoa.h>

@interface WindowControllerSubclass : NSObject /* Not even NSWindowController */
@end

MainWindowControllerSubclass.m:

#import "WindowControllerSubclass.h"

@implementation WindowControllerSubclass

- (void) dealloc 
    NSLog(@"%@ deallocd", [self className]);


@end

【问题讨论】:

我希望这是一个错字:[[MainController new] release];。如果没有,这是一个主要问题,因为您会立即释放您正在创建的对象。 目前无法确定(手头没有 Mac),但属性的 getter 可能实现为 [[wc retain] autorelease]。这是 getter 的常见模式。 @Rob。这不是一个错字。我这样做是为了测试 MainController。它需要以正确的顺序显示 nslog。这就是这个简单问题示例的重点。 您可以覆盖控制器中的autorelease 方法,设置断点并查看谁以及何时执行此操作。 @Eugene,你猜对了。请参阅我编辑的答案。 【参考方案1】:

这没什么奇怪的,尤其是当您的 NSWindowController 在自动释放池中时。

一个对象(比如x)在每个拥有它的对象释放它时被释放。 Autorelease 是一个延迟释放,即它实际上直到自动释放池被耗尽时才释放。

考虑以下事件链:

    B creates x
    A owns x
    A autoreleases x. // x is not released; it's put on an autorelease pool
    B releases x.     // x is not dealloced yet, because x is not released by the autorelease pool
    autorelease pool is drained. x is sent another release message. nobody owns x. x is dealloc'd. 

这就是你所看到的。

--- 更新---

更准确地说,autorelease pool 的神秘用法源于你的一行

[self.wc release]; 

这使用wc 的getter,即它调用[self wc]。现在,默认的合成 getter 在this portion of obj-c runtime 中实现,特别是objc_getProperty_non_gc。请注意,您的属性是(retain),即(atmomic retain)。为了保证原子性,getter retains ivar 然后在 autorelease'ing 之后返回它:

id *slot = (id*) ((char*)self + offset);
if (!atomic) return *slot;

// Atomic retain release world
spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
_spin_lock(slotlock);
id value = objc_retain(*slot);
_spin_unlock(slotlock);

// for performance, we (safely) issue the autorelease OUTSIDE of the spinlock.
return objc_autoreleaseReturnValue(value);

这就是为什么它被放到自动释放池中。无论如何,

obj.property=[[SomeClass alloc] init];
[obj.property release];

是个坏主意。在您的情况下,第二行中的 self.ivar 返回了您在第一行中分配的内容,但是在聪明的非综合访问器或多线程环境中不能保证这一点。当你这样做时

obj.property=x;
id y=obj.property;

xy 可以不同,如果obj 做了一些巧妙的缓存,或者如果有另一个线程访问obj 并在两行之间更改obj.property。因此,请改用临时变量:

SomeClass* a=[[SomeClass alloc] init];
obj.property=a;
[a release];

【讨论】:

没错,但我很想知道他的窗口控制器最初是如何进入自动释放池的...... 自动添加。 Cocoa 应用程序中的每个线程都维护自己的 NSAutoreleasePool 对象堆栈。 @Yuji autorelease 从未在我的代码中使用过。不知道它添加到 AR 池中的是什么。见上面的代码。 编译器和/或NSObject的实现可以并且确实在幕后使用NSAutoreleasePool。你不应该关心,只要在下次自动释放池耗尽时所有东西都被清理干净。【参考方案2】:

尝试用[self.windowController release]; 替换self.windowController = nil;(前一种代码类型,即将事物设置为nil,(有时)用于垃圾收集环境,虽然不是在dealloc方法中。)

【讨论】:

你能看一下你用来分配窗口控制器并将它分配给self.windowController的代码吗?我的猜测是:当您分配窗口控制器时,保留计数为 1。当您将其分配给 self.windowController 时,保留计数变为 2(因为 @property 设置为“保留”)。因此,当您释放时,保留计数会回到 1,因此没有释放。如果我是对的,您或许可以改用“@property (assign)”来解决此问题。 它正在释放,它只是在自动释放池中释放。所以保留计数不是这里的问题。这是令人难以置信的事情。 你能贴出你用来分配窗口控制器的代码并将它分配给self.windowController吗? 调用self.windowController == nil 将得到与调用[self.windowController release] 完全相同的结果,只是指针将设置为nil。两者都会释放对象。这不是“问题”的原因。 @Rob 我“发现”了我的问题。但不确定如何解决。基本上,上面的代码显示任何类型的绑定都会导致控制器被放置在自动释放堆栈上。我该如何解决这个问题?【参考方案3】:

您的期望不正确。这就是引用计数内存管理的关键思想:release 从不保证释放;仅当保留它的每个人都决定释放该对象时,该对象才会被释放。如果它仍然保留在某个地方,则无法强制释放。

更新:

首先,属性 getter 实现为 return [[wc retain] autorelease](参见 Yuji 的回答)。

其次,Cocoa 也可以保留和自动释放您的 WindowController。如果现在没有发生,并不意味着当您添加更多代码时不会发生。

你不应该对释放顺序做出假设,或者autorelease 会或不会被调用。您甚至不能假设在运行循环结束时池会立即耗尽,因为可能有自定义运行循环正在运行。

修复您的对象,以便它们可以处理任何可能的释放顺序。

【讨论】:

你误会了。我在任何时候都没有使用自动释放。我正在使用显式内存管理。 一些 Cocoa 代码做到了。您的代码不是唯一保留和释放对象的代码。这就是为什么你不应该对此抱有任何期望的原因。 但我不知道 Autorelease 是从哪里来的。另外,这是不对的。该项目在主对象释放它之后被释放。当它被释放时,它会访问不再分配的内存(在另一个对象中),因为它没有正确释放。 A 包含 B 和 C。B 链接到 C。A 释放 C。A 被释放,并且释放 B 和 C。C 被释放。 B 被自动释放(问题)并试图告诉 C 做某事。除了现在 C 已经发布。 称为悬空指针。 C 的dealloc 必须调用 B 并告诉它它不再存在。自动释放不是问题,它是 Cocoa 中的常见操作;问题是您的对象图无法处理一些可能的解除分配方案。 您的示例会引入更多问题。现在想象 C 试图告诉 B 它已经死了。如果B之前被释放了怎么办? C怎么会知道B? C 怎么知道 B 是活着的还是自动释放的?问题是基金会没有按预期运行。看上面的简单例子。我没有使用 GC,我没有使用自动释放。那么 autorelease 是从哪里来的呢?该示例是直接的 NSObject 示例。没有观察等。它仍然失败。

以上是关于@property(保留)是不是自动释放或释放对象?的主要内容,如果未能解决你的问题,请参考以下文章

将自动释放的对象分配给保留的属性

代表 - 保留或分配 - 释放?

iphone:多次自动释放对象

我应该在重新分配之前调用分配给保留属性的自动释放对象的释放吗?

带有自动释放池的@property 复制和手动内存管理

自动释放对象递减的保留计数何时减少?