我有一个 macOS 应用程序,大多数用户通过将图像拖放到应用程序图标或菜单栏图标上来使用。一些用户还通过终端运行以下命令来使用我的应用程序:
open -a /Users/username/Library/Developer/Xcode/DerivedData/AppName-bomyuotvsgqtachwwiidvpiaktgc/Build/Products/Debug/AppName.app /Users/username/Downloads/image.jpeg
我的应用程序处理在 AppDelegate 的 func application(_ sender: NSApplication, openFiles filenames: [String])
方法中传递的链接。
到目前为止效果很好。如果我的应用程序已打开,MacOS 仍会使用新图像路径调用 openFiles
,并且我的应用程序会打开一个新窗口来显示它。这一切都运作良好。
现在我希望用户能够将某些参数传递给我的应用程序。例如这个:
open -a /Users/username/Library/Developer/Xcode/DerivedData/AppName-bomyuotvsgqtachwwiidvpiaktgc/Build/Products/Debug/AppName.app /Users/username/Downloads/image.jpeg --args full
在这里我想接收完整
参数。我阅读了其他一些使用 CommandLine.arguments
API 的文章。但这似乎不包含论据。每次 CommandLine.arguments
的值等于:
["/Users/username/Library/Developer/Xcode/DerivedData/AppName-bomyuotvsgqtachwwiidvpiaktgc/Build/Products/Debug/AppName.app/Contents/MacOS/AppName", "-NSDocumentRevisionsDebugMode", "YES"]
我认为这是因为 CommandLine.arguments
仅在应用程序最初启动且参数传递到 main
函数时才起作用。但对于已打开的应用程序,这些不会被传递,因为不会再次为已运行的应用程序调用 main
。
如何实现这一目标?
最佳答案
使用 open
命令的 -n 选项,您始终会获得一个可以通过这种方式接收参数的应用程序的新实例。但是,您可能不希望您的应用程序有多个实例。
这意味着您需要一个小型命令行程序,如果应用程序已在运行,则该程序可以使用参数执行请求。如果应用程序尚未运行,它可能应该使用参数启动应用程序。
命令行程序和 GUI 应用程序之间进行通信的方式有多种。根据具体要求,它们具有相应的优点和缺点。
命令行工具
但是,实际过程始终是相同的。这里在 DistributedNotificationCenter 的帮助下作为示例,命令行工具可能如下所示:
import AppKit
func stdError(_ msg: String) {
guard let msgData = msg.data(using: .utf8) else { return }
FileHandle.standardError.write(msgData)
}
private func sendRequest(with args: [String]) {
if let json = try? JSONEncoder().encode(args) {
DistributedNotificationCenter.default().postNotificationName(Notification.Name(rawValue: "\(bundleIdent).openRequest"),
object: String(data: json, encoding: .utf8),
userInfo: nil,
deliverImmediately: true)
}
}
let bundleIdent = "com.software7.test.NotificationReceiver"
let runningApps = NSWorkspace.shared.runningApplications
let isRunning = !runningApps.filter { $0.bundleIdentifier == bundleIdent }.isEmpty
let args = Array(CommandLine.arguments.dropFirst())
if(!isRunning) {
if let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: bundleIdent) {
let configuration = NSWorkspace.OpenConfiguration()
configuration.arguments = args
NSWorkspace.shared.openApplication(at: url,
configuration: configuration,
completionHandler: { (app, error) in
if app == nil {
stdError("starting \(bundleIdent) failed with error: \(String(describing: error))")
}
exit(0)
})
} else {
stdError("app with bundle id \(bundleIdent) not found")
}
} else {
sendRequest(with: args)
exit(0)
}
dispatchMain()
注意:由于 DistributedNotificationCenter 无法再使用当前 macOS 版本发送 userInfo,因此参数将简单地转换为 JSON 并与对象参数一起传递。
应用程序
然后,实际应用程序可以使用 applicationDidFinishLaunching 来确定它是否是使用参数重新启动的。如果是,则对参数进行评估。它还为通知注册一个观察者。收到通知后,JSON 会转换为参数。在这两种情况下,它们都只是显示在警报中。可能看起来像这样:
import Cocoa
@main
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
let bundleIdent = "com.software7.test.NotificationReceiver"
DistributedNotificationCenter.default().addObserver(self,
selector: #selector(requestReceived(_:)),
name: Notification.Name(rawValue: "\(bundleIdent).openRequest"),
object: nil)
let args = Array(CommandLine.arguments.dropFirst())
if !args.isEmpty {
processArgs(args)
}
}
private func processArgs(_ args: [String]) {
let arguments = args.joined(separator: "\n")
InfoDialog.show("request with arguments:", arguments)
}
@objc private func requestReceived(_ request: Notification) {
if let jsonStr = request.object as? String {
if let json = jsonStr.data(using: .utf8) {
if let args = try? JSONDecoder().decode([String].self, from: json) {
processArgs(args)
}
}
}
}
}
struct InfoDialog {
static func show(_ title: String, _ info: String) {
let alert = NSAlert()
alert.messageText = title
alert.informativeText = info
alert.alertStyle = .informational
alert.addButton(withTitle: "OK")
alert.runModal()
}
}
如前所述,应根据具体要求选择适当的进程间通信方法,但流程始终大致相同。
测试
关于MacOS 应用程序 - 为什么已打开的应用程序的 CommandLine.arguments 不包含传递的 --args 参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66719941/