在Mac上实现单实例应用程序的理想方法

8
在Windows上,常见的做法是创建命名互斥量,并使用其存在来确定给定应用程序的实例是否已经在运行。这种方法有其缺点,但大多数情况下都有效。
我可以想到几种在Mac上实现此功能的方法:
1. 命名pthread互斥量 2. 枚举运行的进程并查找匹配项 3. 创建和锁定文件
Cocoa / Carbon中是否内置了比上述选项更容易的方法?如果没有,那么在Mac上最常用的是哪三个?我会认为是2或3...

仅仅从应用程序的启动和交互方式来看,我认为不可能有多个实例存在...至少不是在捆绑成一个漂亮的.app文件的应用程序中。 - Cogsy
好吧,我在我的问题中有点作弊...我真的想运行一个进行了大量验证的测试应用程序,但在应用程序正在运行时启动它会导致误报。为了不浪费时间追踪无用问题,如果应用程序正在运行,我希望测试应用程序以错误退出。在Windows上,我使用了单实例方法来解决这个问题,并假设我想在Mac上执行相同的操作。 - psychotik
使用NSRunningApplication检测是否有相同bundleID的应用程序正在运行,如果是,则激活它并关闭启动的应用程序。 - Roman Solodyashkin
7个回答

9

进一步阐述如何使用NSWorkspace。尝试使用NSWorkspace中的launchedApplications。它返回一个包含每个已启动应用程序的字典的NSArray。您可以循环遍历该数组,以查看是否已经运行了您要查找的应用程序。我建议您使用具有键NSApplicationBundleIdentifier的值,该值将具有类似于“com.mycompany.myapp”的值,而不是查找名称。如果您需要查找应用程序的捆绑标识符,可以查看其应用程序包中的info.plist文件。


这差不多是我想做的,但我的测试应用程序不是Cocoa应用程序(请参见上面我更新的评论)。 我在Carbon中找到了GetNextProcess()可能会有帮助..还有其他建议吗? - psychotik
如果你是Carbon应用程序,那就是做法。你是否已经链接到了Carbon库?然而,同样的单一实例特性也适用于Carbon应用程序。听起来你可能正在构建一个Unix命令行程序,是这样吗?更详细地描述你正在构建什么可能是有帮助的。 - Jon Steinmetz
请注意,NSWorkspace -launchedApplications在Mac OS X 10.7中已被弃用。苹果建议您现在使用NSWorkspace -runningApplications代替(10.6+):http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/ApplicationKit/Classes/NSWorkspace_Class/Reference/Reference.html#//apple_ref/occ/instm/NSWorkspace/runningApplications - Dave

6

与Windows不同,Macs没有像实例那样的东西。一般来说,如果您想让应用程序运行两次,则需要复制二进制文件并双击复制版本。

如果您需要两个应用程序实例在运行,则您的思维方式不太像Mac用户: )。

编辑:从技术上讲,这是不正确的,请查看评论。


5
换句话说,如果我们讨论GUI应用程序,就没有必要了,因为它已经可以这样工作。 - Chuck
19
不是这样的。例如,如果您从终端启动应用程序,则可以同时启动N个实例。它不常见并不意味着不能考虑这种情况。 - Dev
好的,我甚至会给你提供一个简单的例子,我相信这是一个非常常见的例子:从安装程序的postinstall启动应用程序。如果您从Spotlight搜索该应用程序并启动它,那么第二个实例会点亮。 - Nikolay Tsenkov

1

在不同操作系统之间进行映射过程管理是行不通的。或者说效果不佳。默认情况下,您只能获得一个应用程序副本。

以下是一个类似的问题,比当前问题更进一步,并且有一些回复讨论了当图像有多个副本或需要协调多个应用程序时的互锁。

如何检测 OS X 应用程序是否已启动

关于运行时上下文的介绍,特别是关于 Mac OS X 守护程序和代理程序(以及当您确实需要运行多个可执行文件副本时,例如作为池或类似于 Apache),请参见:

http://developer.apple.com/technotes/tn2005/tn2083.html


1
如果您正在编写Cocoa应用程序,可以使用NSWorkspace来查看是否有另一个进程正在运行与您的包标识符相同的应用程序。实际上,我见过一些应用程序会弹出对话框并显示:“此应用程序的一个实例已经在运行中” - 我认为Firefox就是这样做的。
虽然这不是非常“Mac风格”的方法,但它可以完成工作。

这差不多是我想做的,但我的测试应用程序不是Cocoa应用程序(请参见上面我更新的评论)。 我在Carbon中找到了GetNextProcess(),可能会有所帮助..还有其他建议吗? - psychotik

0
这是退出当前应用程序的 Swift 5 代码,如果已经在运行,则将其放入 applicationDidFinishLaunching 中即可。
let bundleIdentifier = Bundle.main.bundleIdentifier

if NSWorkspace.shared.runningApplications.filter { $0.bundleIdentifier == bundleIdentifier }.count > 1 {
    print("App already running.")
    exit(0)
}


0

0
以下代码可用于退出已经运行的具有相同包标识符的应用程序。此外,它在执行操作后还会显示一个警报。

AppDelegate.applicationDidFinishLaunching

let runningApp =
    NSWorkspace.shared.runningApplications
        .filter { item in item.bundleIdentifier == Bundle.main.bundleIdentifier }
        .first { item in item.processIdentifier != getpid() }

if let running = runningApp {
    running.forceTerminate()
    
    let alert = NSAlert()
    alert.messageText = "App was alreday running"
    alert.informativeText = "App was terminated."
    alert.alertStyle = NSAlert.Style.informational
    alert.addButton(withTitle: "OK")
    alert.runModal()
}

注意:这假设只能有一个已经运行的应用程序。对于其他情况进行调整应该很简单。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接