我已经在Xcode 9中使用Swift创建了一个基本的Mac OS X Cocoa应用程序。该应用程序从用户那里收集源和目标,然后将数据从源传输到目标。通过启动rsync脚本作为进程来完成数据传输:
let path = "/bin/bash"
let arguments = ["/path/to/backup.sh", sourcePath, destinationPath]
task = Process.launchedProcess(launchPath: path, arguments: arguments as! [String])
问题在于,当传输正在运行时,应用程序会旋转彩虹轮,并且无法使用GUI。这样就无法使用“取消”按钮或功能进度条。
传输完成后,应用将再次运行,并且所有测试输出(例如,打印语句,进度条)都会通过(而不是像预期的那样在备份运行时进行)。
我认为线程可能有助于解决此问题,因此我研究了有关苹果线程的帖子:https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html
但是,本文中的代码在Xcode中似乎没有正确的语法,因此我不确定如何继续使用线程。
我已经走到了尽头,任何帮助将不胜感激!
运行应用程序后,在调试器中暂停,然后在调试器控制台中键入“ bt”,即为输出:
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
frame #0: 0x00007fff65fc120a libsystem_kernel.dylib`mach_msg_trap + 10
frame #1: 0x00007fff65fc0724 libsystem_kernel.dylib`mach_msg + 60
frame #2: 0x00007fff3dac4045 CoreFoundation`__CFRunLoopServiceMachPort + 341
frame #3: 0x00007fff3dac3397 CoreFoundation`__CFRunLoopRun + 1783
frame #4: 0x00007fff3dac2a07 CoreFoundation`CFRunLoopRunSpecific + 487
frame #5: 0x00007fff3cda0d96 HIToolbox`RunCurrentEventLoopInMode + 286
frame #6: 0x00007fff3cda0b06 HIToolbox`ReceiveNextEventCommon + 613
frame #7: 0x00007fff3cda0884 HIToolbox`_BlockUntilNextEventMatchingListInModeWithFilter + 64
frame #8: 0x00007fff3b053a73 AppKit`_DPSNextEvent + 2085
frame #9: 0x00007fff3b7e9e34 AppKit`-[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 3044
frame #10: 0x00007fff3b048885 AppKit`-[NSApplication run] + 764
frame #11: 0x00007fff3b017a72 AppKit`NSApplicationMain + 804
* frame #12: 0x000000010000a88d Mac Syncy`main at AppDelegate.swift:12
frame #13: 0x00007fff65e7a015 libdyld.dylib`start + 1
frame #14: 0x00007fff65e7a015 libdyld.dylib`start + 1
答案 0 :(得分:2)
所以,我发现了几件事:
launchedProcess
使该过程立即启动。而是使用init
来获得更多控制权waitForExit
,则当前线程将一直等到进程结束。因此,让我们开始(最后完全使用View Controller):
task = Process.init()
task.launchPath = path
task.arguments = arguments
task.currentDirectoryPath = workDir
// not running yet, so let's start it:
task.lauch()
DispatchQueue.global().async {
print ("wating for exit")
self.task.waitUntilExit()
}
您必须终止任务,例如在应用程序委托的applicationWillTerminate
中。
不过,您应该意识到,这可能会使您的(rsync)操作保持未定状态-文件/目录仅被半复制等。
我认为提供进度指示器的唯一方法是解析Process(task.standardOutput
)的输出并检查Wheatr rsync在这里提供有用的信息。但这是一个全新的故事,抱歉,这里没有代码。
这是一个具有开始和取消按钮的视图控制器。 请记住,对于已发布的应用程序,您将必须提供更多的错误检查。
class ViewController: NSViewController {
var task:Process!
var out:FileHandle?
var outputTimer: Timer?
@IBAction func startPressed(_ sender: NSButton) {
print("** starting **")
let path = "/bin/bash"
let workDir = "/path/to/working/folder"
let sourcePath = "source"
let destinationPath = "destination"
let arguments = ["backup.sh", sourcePath, destinationPath]
task = Process.init()
task.launchPath = path
task.arguments = arguments
task.currentDirectoryPath = workDir
self.task.launch()
DispatchQueue.global().async {
// this runs in a worker thread, so the UI remains responsive
print ("wating for exit")
self.task.waitUntilExit()
DispatchQueue.main.async {
// do so in the main thread
if let timer = self.outputTimer {
timer.invalidate()
self.outputTimer = nil
}
}
}
}
@IBAction func cancelPressed(_ sender: NSButton) {
print("** cancelling **")
if let timer = self.outputTimer {
timer.invalidate()
self.outputTimer = nil
}
task.interrupt()
}
}