2009-03-26 7 views
19

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

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

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

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

Идеи?

+0

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

+0

Данное приложение является игрой. Запустив несколько копий игры на одной машине, игрок будет иметь несправедливое преимущество перед другими игроками в некоторых ситуациях. –

ответ

8

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

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

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

+0

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

1

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

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

+2

Будьте осторожны, чтобы вы не нарушили способность своего приложения работать одновременно с несколькими пользователями. Приложение, которое завершает работу, когда другой пользователь уже использует его, не работает. –

3

Прежде всего, это «Mac OS X» или «OS X». Нет такой вещи, как «OS/X».

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

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

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

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


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

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

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

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

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

+0

Думаю, он имел в виду копию вместо переименования. В любом случае, вы можете открыть второй экземпляр, используя «open -n TextEdit.app» –

+0

Или запустить -m, если у вас установлен запуск Nicholas Riley. –

3

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

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

24

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

+2

В OS 10.8 выполнение этой проверки прямо при запуске в main.m не работает, потому что на этом этапе запущенное приложение уже не находится в массиве (возможно, только регистрируется в следующей runloop или около того), поэтому вместо " > 1 "вам нужно будет проверить"> 0 ". Чтобы играть в это безопасное w.r.t. в будущих версиях лучше всего проверить массив для текущего приложения: 'for (NSRunningApplication * runningApp in runningApplications) { if (! [runningApp isEqual: [NSRunningApplication currentApplication]]) { // Оповещение и выход }}' –

+2

Еще одна важная проблема: runningApplicationsWithBundleIdentifier возвращает запущенные приложения, соответствующие идентификатору bundleID, но, в решающей степени, только те, которые принадлежат текущему пользователю (поэтому эти решения не будут препятствовать одновременному запуску вашего приложения на этом компьютере. –

7

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

+4

Этот ключ (LSMultipleInstancesProhibited) работает хорошо, когда приложение запускается с Launchpad или Finder. В качестве бонуса уже запущенное приложение выведено на передний план. Для меня это лучше, чем отображение диалогового окна с ошибкой. Ключ не работает, когда приложение запускается из команды линия. – Maf

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; 
    } 
1

Это сочетание ответов римлян и Джефф для Swift 2.0: Если другой экземпляр приложения с тем же расслоением ID уже запущена, появится оповещение, активировать другой экземпляр и закройте дубликат экземпляра.

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

    /* ... */ 
} 
0

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

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) 
     } 
     /* ... */ 
}