Как определить, запущено ли приложение OS X

Обычно пакет приложений на OS X можно запускать только один раз, однако, просто скопировав комплект, одно и то же приложение можно запустить дважды. Какая лучшая стратегия для обнаружения и прекращения этой возможности?

В Windows этот эффект может быть достигнут приложением, создающим именованный ресурс при запуске, а затем выйдите, если именованный ресурс не может быть создан, что указывает на то, что выполняется другой процесс, который уже создал тот же ресурс. Эти ресурсы выпускаются надежным способом в Windows, когда приложение завершает работу.

Проблема, которую я видел при изучении этого, заключается в том, что API-интерфейсы OS X сохраняют состояние в файловой системе и, таким образом, делают стратегию, используемую для Windows ненадежной, т.е. затяжные файлы после неправильного выхода могут ложно указывать, что приложение уже запущено.

API, которые я могу использовать для достижения такого же эффекта на OS X, это: posix, carbon и boost.

Идеи?

Ответ 1

Низкоуровневое решение - использовать flock().

Каждый экземпляр попытается заблокировать файл при запуске, и если блокировка завершится неудачно, другой экземпляр уже запущен. Стаи автоматически отпускаются, когда ваша программа выходит, поэтому не стоит беспокоиться о устаревших замках.

Обратите внимание, что независимо от решения, которое вы выбираете, вам необходимо принять осознанное решение о том, что означает "несколько экземпляров". В частности, если несколько пользователей одновременно запускают ваше приложение, это нормально?

Ответ 2

Это очень легко в 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.

Ответ 3

Там таинственный ключ Info.plist, называемый "Приложение, запрещающее несколько экземпляров", но, похоже, для меня это не работает. Я пишу приложение CLI и выполняю его изнутри пакета. Возможно, это будет работать в графическом приложении, но я не пробовал.

Ответ 4

Во-первых, это "Mac OS X" или "OS X". Нет такой вещи, как "OS/X".

Во-вторых, Mac OS X не поставляется с Boost; вам необходимо связать его с вашим приложением.

В-третьих, большая часть Carbon недоступна в 64-разрядной версии. Это явный сигнал, что эти части Carbon уйдут когда-нибудь (когда Apple откажется от 32-битного в своем оборудовании). Рано или поздно вам придется либо переписать приложение с помощью Cocoa, либо отказаться от Mac.

Обычно пакет приложений в OS/X можно запускать только один раз, однако, просто переименовав пакет, одно и то же приложение можно запустить дважды.

Нет, не может. Запуск переименованного или перемещенного приложения просто активирует (выведет на передний план) процесс, который уже запущен; он не начнет новый, второй процесс вместе с первым.


Существует несколько способов узнать, запущено ли приложение. В каждом случае вы делаете это при запуске:

  • Используйте Cocoa NSConnection для регистрации соединения с единственным именем константы. Это произойдет, если имя уже зарегистрировано. (Вы можете использовать Foundation из приложения Carbon, это Application Kit, с которым вы должны быть осторожны.)
  • Используйте диспетчер процессов для сканирования списка процессов для процессов, идентификатор пакета которых соответствует тому, который вы ищете. Идентификатор пакета не является неизменным, но его сложнее изменить, чем имя файла или местоположение.
  • Если вы хотите посмотреть, когда кто-то запускает вторую копию себя, вы можете использовать CFNotificationCenter:

    • Добавьте себя в качестве наблюдателя для "com.yourdomain.yourappname.LaunchResponse".
    • Отправьте уведомление под именем "com.yourdomain.yourappname.LaunchCall".
    • Добавьте себя в качестве наблюдателя для "com.yourdomain.yourappname.LaunchCall".

    В своем обратном вызове наблюдения для уведомления о вызове отправьте уведомление о ответе.
    В вашем обратном вызове наблюдения для уведомления об ответе выйдите.

    Таким образом, когда начнется первый процесс, он вызовет и не получит ответ; когда начинается второй процесс, он будет вызывать, получить ответ от первого процесса и выйти из уважения к первому.

Ответ 5

Как уже упоминалось, приложения Cocoa обычно не позволяют запускать более одного экземпляра за раз.

В общем, способ Cocoa, чтобы решить этот взгляд на запущенные приложения в NSWorkspace. Это возвращает NSArray, содержащий словарь для каждого запущенного приложения. Вы можете пройти через массив, чтобы узнать, работает ли приложение, которое вы ищете. Я бы посоветовал использовать значение с ключом NSApplicationBundleIdentifier, который будет иметь значение, например "com.mycompany.myapp", а не искать имя. Если вам нужно найти идентификатор пакета для приложения, вы можете посмотреть его файл info.plist в пакете приложений.

Ответ 6

Как насчет IPC? Вы можете открыть сокет и договориться с другим запущенным экземпляром. Вы должны быть осторожны, однако, что он работает, если оба приложения запускаются в одно и то же время.

Я не могу предоставить вам пример кода, так как у меня нет (пока, но я скоро) его использовал.

Ответ 7

Это комбинация ответов Romans и Jeff для Swift 2.0: если еще один экземпляр приложения с тем же идентификатором пакета уже запущен, покажите предупреждение, активируйте другой экземпляр и закройте дубликат экземпляра.

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)
    }

    /* ... */
}

Ответ 8

определить, работает ли приложение с таким же идентификатором 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;
    }

Ответ 9

Это версия seb для Swift 3.0. Если еще один экземпляр приложения с тем же идентификатором пакета уже запущен, покажите предупреждение, активируйте другой экземпляр и закройте экземпляр дубликата.

func applicationDidFinishLaunching(aNotification: NSNotification) {
    /* Check if another instance of this app is running. */
    let bundleID = Bundle.main.bundleIdentifier!
    if NSRunningApplication.runningApplications(withBundleIdentifier: bundleID).count > 1 {
         /* Show alert. */
         let alert = NSAlert()
         alert.addButton(withTitle: "OK")
         let appName = Bundle.main.object(forInfoDictionaryKey: kCFBundleNameKey as String) as! String
         alert.messageText = "Another copy of \(appName) is already running."
         alert.informativeText = "This copy will now quit."
         alert.alertStyle = NSAlert.Style.critical
         alert.runModal()

         /* Activate the other instance and terminate this instance. */
         let apps = NSRunningApplication.runningApplications(withBundleIdentifier: bundleID)
             for app in apps {
                  if app != NSRunningApplication.current {
                      app.activate(options: [.activateAllWindows, .activateIgnoringOtherApps])
                      break
                  }
             }
                NSApp.terminate(nil)
         }   
       /* ... */
}