1

プログラムで 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_ACCESSobjc_msgSendfromで再現可能NSApplicationMainですmain

私は知っています-私はただ行って問題を悪化させただけです。私は正しい方向に一歩進んでいると感じています - 少なくともメニューのすべてが同じスレッドにあります…</p>

明らかな間違いであることを願っています。NSMenu が多数のスレッド処理に巻き込まれた場合、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];
        }
    }
}
4

1 に答える 1

0

@trojanfoe の助けと彼がくれたヒントのおかげで、私はこれを自分で見つけて修正することができました。

この問題は、connectedDevices 配列の構造に起因していました。この配列の各ディクショナリ内の項目の 1 つは DeviceName と呼ばれ、これはメニューの名前を提供するために使用されます。DeviceName は一連の規則に従って、NSMutableString を使用して構築されます。これを割り当てて使用し、connectedDevices 配列の辞書に入れます。

私の理解は、明らかに欠陥がありましたが、NSArray には、そこに配置されたオブジェクトのコピーが含まれるというものでした。この場合、そうではないようです - NSMutableString を解放するとメニューがクラッシュするからです。私がそれを放さなければ(木に触れて)、すべて問題ないようです。

ただし、スレッドコードがこの問題を引き起こした理由を説明することはできません。そして、クラッシュの場所がタイヤを破裂させた釘から数マイル離れている可能性があることを示しています.

この問題を解決するためにご協力いただきありがとうございます。

于 2013-03-06T22:43:58.233 に答える