我正在尝试以编程方式更新 NSMenu。问题是更新 NSMenu 的代码不在主线程中运行。我最初忘记了这个小细节 - 结果是每当另一个线程尝试更新菜单时我的代码就会崩溃。我试图通过明确地让更新代码在主线程中运行来解决这个问题。
更新菜单的代码都在 AppDelegate 中,看起来像这样:
-(void)buildMenu{
dispatch_block_t codeForExecutionOnMainThread = ^{
//Need to empty menu
int i=[devicemenu numberOfItems]-7; //7 is the number of permanent menu items.
while (i-->0)
[devicemenu removeItemAtIndex:i];
//Need to iterate through connectedDevices Array
NSEnumerator *e = [connectedDevices objectEnumerator];
id device;
while (device = [e nextObject]){
[self newMenuItem:[device objectForKey:@"DeviceName"]
parentMenu:devicemenu
deviceID:[[device objectForKey:@"DeviceID"] unsignedIntValue]];
}
};
if ([NSThread isMainThread]){
codeForExecutionOnMainThread();
}
else{
dispatch_sync(dispatch_get_main_queue(), codeForExecutionOnMainThread);
}
}
令人讨厌的是,它只是移动了崩溃。虽然最初的崩溃发生在请求构建菜单的线程上,但它现在发生在主线程上(正如人们所预料的那样)。此外,现在只要单击 NSMenu 就会发生崩溃(以前 NSMenu 显示正常 - 只是无法从中删除条目)。
devicemenu 在 AppDelegate 中声明如下:
@interface AppDelegate : NSObject <NSApplicationDelegate,NSUserNotificationCenterDelegate>
{
IBOutlet NSMenu *devicemenu;
}
devicemenu 的其余部分内置于界面生成器中。
绝对清楚,崩溃不在此代码中。只是如果我从这段代码中删除线程的东西,那么当我单击菜单时不会发生崩溃(相反,当从菜单中删除条目时会发生崩溃)。有时代码也不会崩溃——但在这种情况下 NSMenu 也不起作用。
我得到的崩溃,当它确实崩溃而不是什么都不做时,可以在 Thread 1: 中重现EXC_BAD_ACCESS
,objc_msgSend
来自NSApplicationMain
from main
。
我知道 - 我只是走了,让问题变得更糟。我觉得我正在朝着正确的方向迈出一步 - 至少菜单的所有内容都在同一个线程中......</p>
我希望这只是一个明显的错误。当 NSMenu 被包裹在一堆线程的东西中时,是什么阻止了它的工作?最后,如果它是相关的,我的应用程序没有窗口 - 它只是菜单,菜单位于菜单栏的右侧。
堆栈跟踪如下:
* thread #1: tid = 0x1d07, 0x96edba87 libobjc.A.dylib`objc_msgSend + 23, stop reason = EXC_BAD_ACCESS (code=2, address=0x11e)
frame #0: 0x96edba87 libobjc.A.dylib`objc_msgSend + 23
frame #1: 0x95aa75d4 CoreFoundation`CFStringCreateCopy + 84
frame #2: 0x965485f4 HIToolbox`_InsertMenuItemTextWithCFString(MenuData*, __CFString const*, unsigned short, unsigned long, unsigned long) + 34
frame #3: 0x96378924 HIToolbox`InsertMenuItemTextWithCFString + 63
frame #4: 0x93e6044a AppKit`-[NSCarbonMenuImpl _carbonMenuInsertItem:atCarbonIndex:] + 550
frame #5: 0x94088e76 AppKit`-[NSCarbonMenuImpl _privatePopulateCarbonMenu] + 237
frame #6: 0x9408fac8 AppKit`-[NSCarbonMenuImpl _populatePrivatelyIfNecessary] + 67
frame #7: 0x9408fa73 AppKit`-[NSCarbonMenuImpl _checkoutMenuRefWithToken:creating:populating:] + 330
frame #8: 0x9411b909 AppKit`-[NSCarbonMenuImpl _maximumSizeForScreen:] + 73
frame #9: 0x942954a4 AppKit`-[NSMenu size] + 60
frame #10: 0x94397191 AppKit`+[NSStatusBarButtonCell popupStatusBarMenu:inRect:ofView:withEvent:] + 427
frame #11: 0x94396d0e AppKit`-[NSStatusBarButtonCell trackMouse:inRect:ofView:untilMouseUp:] + 142
frame #12: 0x93f50db9 AppKit`-[NSControl mouseDown:] + 867
frame #13: 0x93f48a21 AppKit`-[NSWindow sendEvent:] + 6968
frame #14: 0x94397b19 AppKit`-[NSStatusBarWindow sendEvent:] + 75
frame #15: 0x93f43a0f AppKit`-[NSApplication sendEvent:] + 4278
frame #16: 0x93e5d72c AppKit`-[NSApplication run] + 951
frame #17: 0x93e006f6 AppKit`NSApplicationMain + 1053
frame #18: 0x000042cb DeviceMenu`main(argc=3, argv=0xbffffac0) + 43 at main.m:13
这是菜单被填充的地方。正如我所说,所有这些东西都可以正常工作——只要线程不存在。但是线程需要在那里才能移除设备。最令人沮丧。感谢您抽出时间来看看这个。
- (void)newMenuItem:(NSString*)menuName
parentMenu:(NSMenu*)parentMenu
deviceid:(unsigned)deviceid
parentid:(unsigned)parentid
fullid:(unsigned)fullid
{
if (((deviceid&0x00ffffff)==0) && (parentid==0))
{
NSMenuItem *newMenu = [[NSMenuItem alloc] initWithTitle:menuName
action:@selector(deviceClicked:)
keyEquivalent:@""];
[newMenu setTarget:self];
[newMenu setTag:deviceid>>24&0xff];
[newMenu setRepresentedObject:[NSNumber numberWithUnsignedInt:fullid]];
[parentMenu insertItem:newMenu atIndex:0];
}
else
{
//child
unsigned parentTag=(parentid==0)?(deviceid>>24&0xff):deviceid>>28&0xf;
unsigned shift=(parentid==0)?8:4;
unsigned mytag=(deviceid<<shift)>>28;
bool lastItem = !((deviceid<<shift)<<4);
if (![[parentMenu itemWithTag:parentTag] hasSubmenu])
{
//need to add the submenu here
NSMenu *submenu = [[NSMenu alloc] init];
[submenu addItemWithTitle:[[parentMenu itemWithTag:parentTag] title]
action:@selector(deviceClicked:)
keyEquivalent:@""];
[[parentMenu itemWithTag:parentTag] setSubmenu:submenu];
}
if(lastItem)
{
NSMenuItem *newMenu = [[NSMenuItem alloc] initWithTitle:menuName
action:@selector(deviceClicked:)
keyEquivalent:@""];
[newMenu setTarget:self];
[newMenu setTag:mytag];
[newMenu setRepresentedObject:[NSNumber numberWithUnsignedInt:fullid]];
[[[parentMenu itemWithTag:parentTag] submenu] insertItem:newMenu atIndex:1];
}
else
{
[self newMenuItem:menuName parentMenu:[[parentMenu itemWithTag:parentTag]submenu] deviceid:deviceid<<shift parentid:mytag fullid:fullid];
}
}
}