MacOS 应用程序 - 为什么已打开的应用程序的 CommandLine.arguments 不包含传递的 --args 参数?

标签 macos cocoa appdelegate macos-catalina

我有一个 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()
    }
        
}

如前所述,应根据具体要求选择适当的进程间通信方法,但流程始终大致相同。

测试

Test Call

关于MacOS 应用程序 - 为什么已打开的应用程序的 CommandLine.arguments 不包含传递的 --args 参数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66719941/

相关文章:

macos - 当用户聚焦/编辑 NSTextField 时,通过代码更改 NSTextField 的内容

Cocoa shell命令权限

iphone - 从未调用过 shouldStartLoadWithRequest 委托(delegate)方法

xcode - 未迁移到沙箱

macos - 为什么我的 cocoa 程序在启动期间获得 EXC_BAD_ACCESS?

swift - 如何在 NSApplicationDelegate 上加载 NSViewController

ios - 什么时候执行App Delegate的方法willTerminate?

python - 使用 wxPython 显示 Mac OS X 滑动对话框

macos - NSDatePicker 错误

macos - vagrant 1.6.3 无法在 mac osx 上安装 berkshelf 2.0.1