22

通常、OS Xのアプリケーションバンドルは1回しか起動できませんが、バンドルをコピーするだけで、同じアプリケーションを2回起動できます。この可能性を検出して阻止するための最良の戦略は何ですか?

Windowsでは、この効果は、アプリケーションが起動時に名前付きリソースを作成し、名前付きリソースを作成できない場合は終了することで簡単に実現できます。これは、同じリソースをすでに作成している別のプロセスが実行されていることを示します。これらのリソースは、アプリケーションが終了すると、Windows上で信頼できる方法で解放されます。

これを調査したときに私が見た問題は、OS XのAPIがファイルシステムの状態を維持するため、Windowsで使用される戦略の信頼性が低くなることです。つまり、不適切な終了後にファイルが長引くと、アプリケーションがすでに実行されていることを誤って示す可能性があります。

OS Xで同じ効果を達成するために使用できるAPIは、posix、carbon、boostです。

アイデア?

4

9 に答える 9

30

これは、Snow Leopard では非常に簡単です。

- (void)deduplicateRunningInstances {
    if ([[NSRunningApplication runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]] count] > 1) {
        [[NSAlert alertWithMessageText:[NSString stringWithFormat:@"Another copy of %@ is already running.", [[NSBundle mainBundle] objectForInfoDictionaryKey:(NSString *)kCFBundleNameKey]] 
                         defaultButton:nil alternateButton:nil otherButton:nil informativeTextWithFormat:@"This copy will now quit."] runModal];

        [NSApp terminate:nil];
    }
}

詳細については、 http://blog.jseibert.com/post/1167439217/deduplicating-running-instances-or-how-to-detect-ifを参照してください。

于 2010-09-22T15:14:19.000 に答える
9

低レベルの解決策は、flock() を使用することです。

各インスタンスは起動時にファイルをロックしようとします。ロックに失敗すると、別のインスタンスが既に実行されています。プログラムが終了すると、群れは自動的に解放されるため、ロックが古くなる心配はありません。

どのソリューションを選択する場合でも、「複数のインスタンス」を持つことの意味について意識的に決定する必要があることに注意してください。具体的には、複数のユーザーが同時にアプリを実行している場合、それは問題ありませんか?

于 2009-03-26T11:33:11.180 に答える
8

「アプリケーションは複数のインスタンスを禁止しています」という謎の Info.plist キーがありますが、私にはうまくいかないようです。CLI アプリケーションを作成し、バンドル内から実行しています。おそらくGUIアプリケーションで動作するでしょうが、私は試していません.

于 2012-03-08T01:58:00.737 に答える
4

すでに述べたように、Cocoa アプリケーションでは通常、一度に複数のインスタンスを実行することはできません。

一般に、この問題を解決するココアの方法は、NSWorkspace のlaunchedApplications を見てください。これは、起動された各アプリケーションの辞書を含む NSArray を返します。配列をループして、探しているアプリが既に実行されているかどうかを確認できます。名前を探すのではなく、「com.mycompany.myapp」のような値を持つキー NSApplicationBundleIdentifier で値を使用することをお勧めします。アプリのバンドル識別子を見つける必要がある場合は、アプリ パッケージ内の info.plist ファイルを確認できます。

于 2009-07-04T22:11:11.057 に答える
3

まず、「Mac OS X」または「OS X」です。「OS/X」というものはありません。

次に、Mac OS X には Boost が付属していません。アプリケーションにバンドルする必要があります。

第 3 に、Carbon のほとんどは 64 ビットでは使用できません。これは、Carbon のこれらの部分がいつか (Apple がハードウェアで 32 ビットを放棄したときに) なくなることを明確に示しています。遅かれ早かれ、アプリを Cocoa で書き直すか、Mac を放棄する必要があります。

通常、OS/X 上のアプリケーション バンドルは 1 回しか起動できませんが、バンドルの名前を変更するだけで、同じアプリケーションを 2 回起動できます。

いいえ、できません。名前を変更または移動したアプリケーションを起動すると、すでに実行されていたプロセスがアクティブ化 (前面に移動) されます。最初のプロセスと並んで新しい 2 番目のプロセスを開始することはありません。


アプリケーションが既に実行されているかどうかを確認するには、いくつかの方法があります。いずれの場合も、起動時にこれを行います。

  1. Cocoa の NSConnection を使用して、単一の定数名で接続を登録します。名前がすでに登録されている場合、これは失敗します。(Foundation は Carbon アプリから使用できます。これは注意が必要な Application Kit です。)
  2. Process Manager を使用して、探しているバンドル ID と一致するプロセスのプロセス リストをスキャンします。バンドル ID は変更できませんが、ファイル名や場所よりも変更が困難です。
  3. 誰かが自分の 2 番目のコピーをいつ実行したかを知りたい場合は、CFNotificationCenter を使用できます。

    1. 「com.yourdomain.yourappname.LaunchResponse」のオブザーバーとして自分を追加します。
    2. 「com.yourdomain.yourappname.LaunchCall」という名前で通知を投稿します。
    3. 「com.yourdomain.yourappname.LaunchCall」のオブザーバーとして自分を追加します。

    Call 通知の監視コールバックで、Response 通知を投稿します。
    Response 通知の監視コールバックで、終了します。

    したがって、最初のプロセスが開始されると、呼び出しが行われ、応答が返されません。2 番目のプロセスが開始すると、呼び出し、最初のプロセスから応答を取得し、最初のプロセスに従って終了します。

于 2009-03-26T08:39:35.763 に答える
2

This is a combination of Romans' and Jeff's answers for Swift 2.0: If another instance of the app with the same bundle ID is already running, show an alert, activate the other instance and quit the duplicate instance.

func applicationDidFinishLaunching(aNotification: NSNotification) {
    /* Check if another instance of this app is running. */
    let bundleID = NSBundle.mainBundle().bundleIdentifier!
    if NSRunningApplication.runningApplicationsWithBundleIdentifier(bundleID).count > 1 {
        /* Show alert. */
        let alert = NSAlert()
        alert.addButtonWithTitle("OK")
        let appName = NSBundle.mainBundle().objectForInfoDictionaryKey(kCFBundleNameKey as String) as! String
        alert.messageText = "Another copy of \(appName) is already running."
        alert.informativeText = "This copy will now quit."
        alert.alertStyle = NSAlertStyle.CriticalAlertStyle
        alert.runModal()

        /* Activate the other instance and terminate this instance. */
        let apps = NSRunningApplication.runningApplicationsWithBundleIdentifier(bundleID)
        for app in apps {
            if app != NSRunningApplication.currentApplication() {
                app.activateWithOptions([.ActivateAllWindows, .ActivateIgnoringOtherApps])
                break
            }
        }
        NSApp.terminate(nil)
    }

    /* ... */
}
于 2015-10-03T11:14:04.943 に答える
1

IPCはどうですか?ソケットを開き、起動された他のインスタンスとネゴシエートできます。ただし、両方のアプリが同時に起動した場合に機能することに注意する必要があります。

まだ (まだですが、すぐに) 使用していないため、サンプル コードを提供することはできません。

于 2009-03-26T08:39:10.117 に答える
0

同じ bundleID を持つアプリケーションが実行されているかどうかを検出し、それをアクティブにして、起動したものを閉じます。

- (id)init method of < NSApplicationDelegate >

    NSArray *apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:[[NSBundle mainBundle] bundleIdentifier]];
    if ([apps count] > 1)
    {
        NSRunningApplication *curApp = [NSRunningApplication currentApplication];
        for (NSRunningApplication *app in apps)
        {
            if(app != curApp)
            {
                [app activateWithOptions:NSApplicationActivateAllWindows|NSApplicationActivateIgnoringOtherApps];
                break;
            }
        }
        [NSApp terminate:nil];
        return nil;
    }
于 2014-05-21T06:23:24.717 に答える