如何将一系列相关的 NSTask 操作包装成离散函数

How do I wrap up a series of related NSTask operations into discrete functions

我正在编写一个 Swift 命令行工具,它使用 NSTaskgit 交互。在最简单的场景中,我想要 运行 三个命令:initadd .commit -m Initial Commit。我打算为每个命令使用一个单独的 NSTask,并希望将每个命令放在它自己的函数中 - 如果任务成功,则为 returning true,否则为 false 't。此设置将使我的 main 函数看起来像这样:

func main() {

    if runInit() {
        if runStage() {
            if runCommit() {
                 NSLog("success!")
            }
        }
    }
}

要完成此操作,三个函数中的每一个都必须在 returning 之前执行以下操作 (i) 启动任务 (ii) 等待它完成,(iii) 获取 [=21= 中的任何内容],以及 (iv) 设置 return 值(truefalse)。这是我在提交阶段得到的:

func runCommit() -> Bool {

    var retval = false

    var commitTask = NSTask()
    commitTask.standardOutput = NSPipe()
    commitTask.launchPath = gitPath
    commitTask.arguments = ["commit", "-m", "Initial Commit"]
    commitTask.currentDirectoryPath = demoProjectURL.path!

    commitTask.standardOutput.fileHandleForReading.readToEndOfFileInBackgroundAndNotify()

    nc.addObserverForName(NSFileHandleReadToEndOfFileCompletionNotification,
        object: commitTask.standardOutput.fileHandleForReading,
        queue: nil) { (note) -> Void in
            // get the output, log it, then...
            if commitTask.terminationStatus == EXIT_SUCCESS {
                retval = true
            } 
    }

    commitTask.launch()
    commitTask.waitUntilExit()

    return retval

}

我的问题本质上是关于 waitUntilExit 是如何工作的,特别是与我注册的通知相结合,使我能够获得输出。 Apple 的文档说:

This method first checks to see if the receiver is still running using isRunning. Then it polls the current run loop using NSDefaultRunLoopMode until the task completes.

当谈到 运行 循环机制时,我有点不知所云,想知道这在这种情况下意味着什么 - 我可以安全地假设我的通知块将 始终 在封闭函数 returns?

之前执行

waitUntilExit returns 当收到 SIGCHILD 信号时 表明子进程已经终止。通知书 当 EOF 从管道读取到子进程时执行块。 指定这些事件中的哪一个先发生。

因此您必须等待两者。有几种可能的解决方案, 这是一个使用 "signalling semaphore",你也可以使用 "dispatch group".

您的代码中的另一个错误是永远不会删除观察者。

func runCommit() -> Bool {

    let commitTask = NSTask()
    commitTask.standardOutput = NSPipe()
    commitTask.launchPath = gitPath
    commitTask.arguments = ["commit", "-m", "Initial Commit"]
    commitTask.currentDirectoryPath = demoProjectURL.path!

    commitTask.standardOutput!.fileHandleForReading.readToEndOfFileInBackgroundAndNotify()

    let sema = dispatch_semaphore_create(0)
    var obs : NSObjectProtocol!
    obs = nc.addObserverForName(NSFileHandleReadToEndOfFileCompletionNotification,
        object: commitTask.standardOutput!.fileHandleForReading, queue: nil) {
            (note) -> Void in
            // Get data and log it.
            if let data = note.userInfo?[NSFileHandleNotificationDataItem] as? NSData,
                let string = String(data: data, encoding: NSUTF8StringEncoding) {
                    print(string)
            }
            // Signal semaphore.
            dispatch_semaphore_signal(sema)
            nc.removeObserver(obs)
    }

    commitTask.launch()
    // Wait for process to terminate.
    commitTask.waitUntilExit()
    // Wait for semaphore to be signalled.
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)
    let retval = commitTask.terminationStatus == EXIT_SUCCESS
    return retval
}