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

时间:2015-10-03 20:55:10

标签: swift cocoa nstask

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

func main() {

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

为了实现这一点,在返回(i)启动任务(ii)等待它完成,(iii)获取stdout中的任何内容,以及(iv)设置之前,三个函数中的每一个必须执行以下操作。返回值(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的文档说:

  

此方法首先检查接收器是否仍在使用isRunning运行。然后,它使用NSDefaultRunLoopMode轮询当前的运行循环,直到任务完成。

当谈到运行循环机制时,我有点超出了我的深度,并且想知道在这种情况下这意味着什么 - 我可以安全地假设我的通知块将始终被执行在封闭函数返回之前?

1 个答案:

答案 0 :(得分:3)

收到waitUntilExit信号后,

SIGCHILD返回 表明子进程已终止。通知 从管道向子进程读取EOF时执行块。 not 指定了哪些事件首先发生。

因此你必须等待两者。有几种可能的解决方案, 这里有一个使用“信号信号量”,你也可以使用 一个“派遣小组”。

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

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
}