NSMenu setTarget method causes intermittent crashes?
Duncan Champney <[email protected]>
| Newsgroups | gmane.comp.macosx.devel |
|---|---|
| Message-ID | <[email protected]> |
Fellow Cocoa developers,
I think I've found a bug in the Cocoa framework, or at least a gaping
hole in the documentation. It occurs in a garbage-collected app that
uses popup menus.
Our app creates several different popup menus.
We have an NSView object that creates an NSMenu object. We then add a
menu item to the newly created menu, and use the menu item's setTarget
method to make the view the target of the menu item.
Finally, we invoke the NSMenu class method popUpContextMenu to display
a popup menu. The whole method (a method of our custom view) looks
like this:
- (void) popupMenuStartWithEvent:(NSEvent*)p_event
onView:(NSView*)p_view
{
if (p_event && p_view && (!myPopupMenu)) {
self.myPopupMenu = [[[JXMenu alloc] initWithTitle:@"Contextual
Menu"] autorelease];
//self.myPopupMenu.myPopupMenuView = self;
NSMenuItem* a_menu_item = [self.myPopupMenu
insertItemWithTitle:@"Beep" action:@selector(popupMenuAction:)
keyEquivalent:@"" atIndex:0];
[a_menu_item setView: self];
[a_menu_item setTarget:self];
[NSMenu popUpContextMenu:self.myPopupMenu withEvent:p_event
forView:p_view];
}
This code works fine. I won't post the details of our particular popup
menu, because they are not relevant.
The problem occurs later, after the NSMenu object and the NSView
object are deallocated by garbage collection.
SOMETIMES, our app crashes deep in system calls. Looking at the stack
trace for the crash, the system appears to be deallocating the
temporary window it uses to manage the popup menu. It seems to cause
the menu item we create to try to send a message to the view we
specify as a target in the setTarget call above. However, that
sometimes takes place after garbage collection has already released
the view, so the message to the menu item's target ends up trying to
send a message to a released object, and causes a crash.
Here is the call stack from a crash taken from the XCode debug window:
#0 0x946c3688 in objc_msgSend
#1 0x92608bd1 in CFArrayGetCount
#2 0x908587ac in _recursiveInvalidateCachedVisibleRectValue
#3 0x908583f9 in -[NSView _setSuperview:]
#4 0x90861f86 in -[NSView removeFromSuperview]
#5 0x908db695 in -[NSView removeFromSuperviewWithoutNeedingDisplay]
#6 0x90b3a713 in customMenuItemViewChangeOwningWindow
#7 0x90b39b15 in NSMenuItemCarbonEventHandler
#8 0x94c2f13d in DispatchEventToHandlers
#9 0x94c2e57b in SendEventToEventTargetInternal
#10 0x94c2e3e0 in SendEventToEventTargetWithOptions
#11 0x94c44c52 in HIView::SendOwningWindowChanged
#12 0x94c44ae1 in HIView::NotifySubtreeWindowChanged
#13 0x94c44b03 in HIView::NotifySubtreeWindowChanged
#14 0x94c44b03 in HIView::NotifySubtreeWindowChanged
#15 0x94c44b03 in HIView::NotifySubtreeWindowChanged
#16 0x94c448d0 in HIView::SetWindowRef
#17 0x94cfa66b in _HIViewSetWindow
#18 0x94c6dff1 in DisposeMenuWindow
The fix, it seems, is to set the menu item's target method to nil
after the call to popUpContextMenu.
The fixed method looks like this:
- (void) popupMenuStartWithEvent:(NSEvent*)p_event
onView:(NSView*)p_view
{
if (p_event && p_view && (!myPopupMenu)) {
self.myPopupMenu = [[[JXMenu alloc] initWithTitle:@"Contextual
Menu"] autorelease];
//self.myPopupMenu.myPopupMenuView = self;
NSMenuItem* a_menu_item = [self.myPopupMenu
insertItemWithTitle:@"Beep" action:@selector(popupMenuAction:)
keyEquivalent:@"" atIndex:0];
[a_menu_item setView: self];
[a_menu_item setTarget:self];
[NSMenu popUpContextMenu:self.myPopupMenu withEvent:p_event
forView:p_view];
[a_menu_item setView: nil];
[a_menu_item setTarget:nil];
}
This is an intermittent problem and hard to reproduce, so I am not
positive that this is the fix.
My guess is that the popup menu code is old carbon (or HI) code, and
it doesn't "play nicely" with garbage collection.
Has anybody else seen this problem?
Duncan Champney
WareTo