Saturday, March 6, 2010

Workaround - Crash in NSPopUpCell performClickWithFrame:inView:

I have been working on contextual menus for the time line. Contextual menus appear to be quite straight forward. If you don't want your contextual menus customized with whatever can add to contextual menus you create a NSPopUpCell - this is described in Apple's developer documentation. What you need to is nicely covered and well explained in Jesper's blog. Jesper kindly even gives you a category that does the leg work for you.

All looks good - except that there is a missing release in Jesper's category. It is not a big deal and I would probably not have mentioned it except that adding the release caused things to crash. Somewhere there was a stale object - but the code is almost trivial. Hitting Google I got a few mentions of the crash (for example here) but no solution.

I did a few things. I created my own subclass of NSPopUpCell with just a dealloc so that I could put a breakpoint on the dealloc. Was the NSPopUpCell being released by performClickWithFrame:inView? It seemed unlikely and was indeded not happening. It was being called (as I would expect) by the release I added. If I did not release the menu that I set [popUpButtonCell setMenu:menu] then the crash did not happen. The next thing I did was to track the owner counts of my menu. They were as I expected incremented by [popUpButtonCell setMenu:menu] and decremented when I released the cell.

I don't know what the cause of the problem is. I suspect that it is bug in AppKit - but this is of course conjecture. The workaround I found was rather than setting the menu, to use the menu that exists in the NSPopUpCell.

NSMenu *menu = [popupCell menu];

Once you have your hands on this menu you can add and remove items - just you don't get the crash when you dispose of the NSPopUpCell.

2 comments:

Anonymous said...

I worked around this in my code by copying the menu, autoreleasing it and the popup cell. It seems to work in limited testing.

Another way could be to keep a single NSPopUpButtonCell around at all times. Release the previous cell if there's been one. That way you'll only ever leak one object, which will go away at the end of the application anyway.

Bad Base said...

Hi Jesper - what you suggest is a nice way of solving the problem. I also imagine that it might be fixed in Snow Leopard or might just not happen if you have garbage collection.

Importantly many thanks for posting the category - it was very helpful.