在标准做法中,谁拥有NSWindowController?

12

在看完《谁来释放NSWindowController对象?》后,我希望能进一步澄清一些问题。

我正在为我的侄子写一个简单的库存管理应用程序。我有一个表格视图显示他们的“图书馆”等内容。要向图书馆添加新项,他们可以点击一个“+”按钮。该按钮打开一个新窗口,提示他们输入项的详细信息,并在点击“确定”时验证输入。

所有这些都工作得很好。但是,我有一个关于内存管理的问题。为了创建新窗口,我使用以下代码:

- (IBAction)addNewItem:(id)sender {
  LibraryItemEditorController *editorController = 
    [[LibraryItemEditorController alloc] 
          initWithWindowNibName:@"LibraryItemEditor"];

  [editorController showWindow:nil];
  // editorController is "leaked" here, it seems.
}

addNewItem:的结尾,我不能释放(也不能自动释放)editorController,因为没有别的东西引用editorController;如果我释放它,窗口就会立即消失。然而,一旦窗口关闭,我希望窗口控制器被释放。在苹果的窗口编程指南中,我读到以下内容:

  

如果您希望窗口关闭时使窗口和窗口控制器都消失,且该窗口不是文档的一部分,则您的NSWindowController子类可以观察NSWindowWillCloseNotification或者作为窗口代理实现windowWillClose:方法并包括以下代码行:

[self autorelease];
我在窗口控制器的windowWillClose:方法中使用了[self autorelease]。虽然这样做是可行的,而且不会造成内存泄漏,但感觉很不好看;addNewItem:方法看起来像在泄露内存,静态分析工具也这么认为。我知道实际上在windowDidClose:已经处理过了,但感觉还是不对。此外,窗口控制器现在在没有保留自身的情况下释放自己。所有这些都违反了我所学习的内存管理规则。
我的另一个选择是在父控制器(可以是NSWindowController或包含NSWindowControllerNSMutableSet)上放置一个ivar,然后在父控制器中监听NSWindowWillCloseNotification并作出响应来释放它。这样更加清晰,也是我将要采用的方式。但这需要做更多的工作,这就引出了我的问题。
监听NSWindowDidCloseNotification是标准的做法吗?管理按需创建和销毁的NSWindowController的标准方法是什么?[self autorelease]的方式是传统推荐的选项吗?只有现在我们有了静态分析工具才会发现这是个问题吗?
2个回答

5
听起来你的窗口是模态的,如果是这样的话:
[NSApp runModalForWindow:[editorController window]];
[editorController release];

这是一种非模态窗口的模式:

@implementation QLPrefWindowController

+ (id) sharedInstance
{
    if (!_sharedInstance)
    {
        _sharedInstance = [[QLPrefWindowController alloc] init];
    }
    return _sharedInstance;
}

- (void)windowWillClose:(NSNotification *)notification
{
    if ([notification object] == [self window] && self == _sharedInstance)
    {
        _sharedInstance = nil;
        [self release];
    }
}

然后,任何想要访问或显示窗口的人都可以通过+sharedInstance类方法来实现。如果窗口尚未可见,则会创建该窗口;否则,他们将获得当前可见的窗口。


我没有将它作为模态窗口运行,但我认为我应该这样做。对于检查器面板,sharedInstance版本是有意义的。如果我真的需要同时打开多个窗口,直接保留它们可能是有意义的。这对我很有用。 - BJ Homer
我不相信这个答案是正确的,因为类方法无法访问实例变量。在这种情况下,类方法“sharedInstance”试图访问名为“_sharedInstance”的iVar。这将每次都失败。 - Bryan
_sharedInstance 将是一个静态变量,而不是实例变量。这通常是共享实例的实现方式。 - BJ Homer

0
上面发布的非模态情况的解决方案是不正确的,因为类方法无法访问 iVars。我通过创建一个类方法(在我的 NSWindowController 子类 LPWindowController 中)来解决这个问题,代码如下:
+ (id) autoreleasingInstanceShowingWindow
{
    LPWindowController *obj = [[LPWindowController alloc] initWithWindowNibName:@"myWindow"];
    [obj showWindow:[NSApp delegate]];

    return obj; 
}

上述方法返回一个保留计数为1的LPWindowController实例。它还显示了控制器的窗口。这很重要,否则我们将不得不依赖调用者调用“showWindow:”来使LPWindowController的窗口出现。如果调用者未能执行此操作(这可能是一个错误),则控制器将永远不会被释放。通过在分配/初始化控制器时强制显示窗口,我们避免了这种陷阱。

接下来,在IB中,我们将窗口的委托设置为LPWindowController类,并在该类中添加以下内容:

- (void) windowWillClose:(NSNotification *)notification
{
    [self autorelease];
}

最后,当我们需要创建和显示窗口时,我们只需使用下面的方法,而不是在LPWindowController上调用“alloc/initWithWindowNibName:”。
LPWindowController *cont = [LPWindowController autoreleasingInstanceShowingWindow];
cont = nil;

第二行非常重要。首先,它消除了关于“未使用的变量cont”的警告。其次,它消除了悬挂指针的危险。一旦LPWindowController实例自己释放,如果没有将cont清空,则它会指向垃圾内存。

无论如何,这是我推荐的解决此问题的方法。


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