Как определить, запущено ли уже приложение OS X - PullRequest
20 голосов
/ 26 марта 2009

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

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

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

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

Идеи

Ответы [ 9 ]

27 голосов
/ 22 сентября 2010

Это очень легко в 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 для получения дополнительной информации.

8 голосов
/ 08 марта 2012

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

8 голосов
/ 26 марта 2009

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

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

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

4 голосов
/ 05 июля 2009

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

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

3 голосов
/ 26 марта 2009

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

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

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

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

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


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

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

    1. Добавьте себя в качестве наблюдателя для «com.yourdomain.yourappname.LaunchResponse».
    2. Разместите уведомление под именем «com.yourdomain.yourappname.LaunchCall».
    3. Добавьте себя в качестве наблюдателя для «com.yourdomain.yourappname.LaunchCall».

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

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

1 голос
/ 10 октября 2017

Это версия 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)
         }   
       /* ... */
}
1 голос
/ 03 октября 2015

Это комбинация ответов Романа и Джеффа для 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)
    }

    /* ... */
}
1 голос
/ 26 марта 2009

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

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

0 голосов
/ 21 мая 2014

определить, запущено ли приложение с тем же идентификатором, активировать его и закрыть то, что запускается.

- (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;
    }
...