NSWindowController的showWindow:方法导致内存泄漏问题

3

我一直在为以下泄漏问题苦苦挣扎。通过Instruments工具,我将其缩小到以下代码块:

- (NewMessageWindowController *)showNewMessageWindowWithRecipients:(NSArray *)recipients {

    NewMessageWindowController * newMessageWindowController = [[NewMessageWindowController alloc] init];
    [newMessageWindowController showWindow:self]; // 100% on this line.
    [newMessageWindowController.toField setStringValue:[recipients componentsJoinedByString:@","]];
    [newMessageWindowController.messageView becomeFirstResponder];
    [windowControllers addObject:newMessageWindowController];
    [newMessageWindowController release];

    return newMessageWindowController;
}

这个块的调用方式如下:

[AppDelegate showNewMessageWindowWithRecipients:[NSArray arrayWithObject:recipient]];

recipient只是一个NSString时。

这里是来自instruments的回溯:

  30 Friendz start
  29 AppKit NSApplicationMain
  28 AppKit -[NSApplication run]
  27 AppKit -[NSApplication sendEvent:]
  26 AppKit -[NSWindow sendEvent:]
  25 AppKit -[NSWindow keyDown:]
  24 AppKit forwardMethod
  23 Friendz -[FriendzAppDelegate showNewMessageWindowWithRecipients:] /Path/To/FriendzAppDelegate.m:226
  22 AppKit -[NSWindowController showWindow:]
  21 AppKit -[NSWindow makeKeyAndOrderFront:]
  20 AppKit -[NSWindow _makeKeyRegardlessOfVisibility]
  19 AppKit -[NSWindow _changeKeyAndMainLimitedOK:]
  18 AppKit -[NSWindow becomeKeyWindow]
  17 AppKit _NXResetCursorState
  16 AppKit +[NSEvent _discardCursorEventsForWindowNumber:criteria:]
  15 HIToolbox FlushSpecificEventsFromQueue
  14 HIToolbox PullEventsFromWindowServer
  13 HIToolbox PullEventsFromWindowServerOnConnection(unsigned int, unsigned char)
  12 HIToolbox ConvertPlatformEventRecordAndPostWithOptions(__CGEvent*, _CGSEventRecord const*, short, unsigned char, unsigned char)
  11 HIToolbox PostEventToQueueInternal
  10 HIToolbox _NotifyEventLoopObservers
   9 HIToolbox KeyEventPostedObserver
   8 HIToolbox TSMProcessRawKeyCode
   7 HIToolbox TSMTranslateKeyEvent
   6 HIToolbox GetDataFromUCHRForEvent
   5 HIToolbox ConvertEventUniCharsToCharCodes
   4 HIToolbox utGetInputSourceScriptInfo
   3 CoreFoundation CFLocaleCreateCanonicalLocaleIdentifierFromScriptManagerCodes
   2 CoreFoundation CFStringCreateWithCStringNoCopy
   1 CoreFoundation __CFStringCreateImmutableFunnel3
   0 CoreFoundation _CFRuntimeCreateInstance

windowControllers是在applicationDidFinishLaunching中进行NSMutableArray的分配和初始化,并在dealloc方法中释放。

NewMessageWindowController中,我使用以下方式通知应用程序委托窗口即将关闭,不再需要保留控制器:

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

应用程序委托的方法看起来像这样:

- (void)windowControllerDidFinish:(NSWindowController *)controller {
    [windowControllers removeObject:controller];
}

记录数组在窗口关闭前和关闭后的状态是我所期望的。控制器在窗口关闭之前存在,当窗口关闭时被移除。
Instruments会在我关闭窗口时检测到内存泄漏。在窗口打开时,一切似乎都正常。值得注意的是,dealloc按预期在NewMessageWindowController中被调用。泄漏并没有报告控制器本身是问题,而是一个NSCFString对象泄漏了,它只是源自上面的代码。
Build and Analyze没有发现任何问题,并且我相信我在创建/显示窗口控制器/窗口的代码块中的内存管理是正确的。
奇怪的是,只有在使用键盘关闭窗口时才会出现泄漏。如果我点击红色关闭按钮,Instruments就不会检测到任何问题。
最后,Instruments并不总是显示该代码块负责。在这些情况下,Instruments没有引用我的任何代码 - 它似乎全部来自AppKit。同样,这只发生在我使用键盘关闭窗口(cmd-w)的情况下。
有什么想法吗?
2个回答

0
在这种情况下,我会使用Xcode4中的Instruments版本,配置分配工具来记录保留/释放事件。这应该可以显示出为什么这个特定控制器的保留计数不会归零。请注意,对于基于鼠标和基于键盘的关闭,执行的代码路径可能不同。

Instruments是在说NewMessageWindowController对象存在泄漏吗?如果是这种情况,并且您看到该对象被dealloced,则意味着您有一个虚假泄漏。尝试等待一段时间后重新运行泄漏扫描;泄漏可能会消失。如果该对象明显已被解除分配,我不会担心这个泄漏。 - Michael Gorbach
Instruments显示一个NSCFString作为泄漏对象,它只是来自showWindow调用(或者Instruments这样说)。 - Tom Irving
奇怪,这看起来是深入到Kit事件处理代码中的。似乎没有什么可以干扰这个内存管理的事情。如果重新采样,泄漏还会继续存在吗?我倾向于认为这只是一个暂时性或错误的泄漏。 - Michael Gorbach
是的,这是100%可以重现的。每次我用键盘关闭窗口时,Instruments都会捕获到一个泄漏。 - Tom Irving
我有一个想法。试着延迟windowController的最终释放(和dealloc),直到windowDidClose:调用返回。似乎你可以通过在从保留它的数组中删除对象之前对其进行保留和自动释放来实现这一点。在willClose:中删除控制器和窗口可能是问题所在。 - Michael Gorbach
显示剩余3条评论

0
这是因为你不能依赖dealloc在对象"销毁"时可靠地被调用吗 - 有可能使用键盘某种原因导致不立即调用dealloc,而点击X则更容易立即调用dealloc?

我发现当我使用键盘或鼠标关闭NewMessageWindowController的窗口时,dealloc方法会被调用。 - Tom Irving

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