So, I have a Swift command-line program:
import Foundation
print("start")
startAsyncNetworkingStuff()
RunLoop.current.run()
print("end")
The code compiles without error. The async networking code runs just fine, fetches all its data, prints the result, and eventually calls its completion function.
How do I get that completion function to break out of above current runloop so that the last "end" gets printed?
Added:
Replacing RunLoop.current.run() with the following:
print("start")
var shouldKeepRunning = true
startAsyncNetworkingStuff()
let runLoop = RunLoop.current
while ( shouldKeepRunning
&& runLoop.run(mode: .defaultRunLoopMode,
before: .distantFuture ) ) {
}
print("end")
Setting
shouldKeepRunning = false
in the async network completion function still does not result in "end" getting printed. (This was checked by bracketing the shouldKeepRunning = false statement with print statements which actually do print to console). What is missing?
For a command line interface use this pattern and add a completion handler to your AsyncNetworkingStuff (thanks to Rob for code improvement):
print("start")
let runLoop = CFRunLoopGetCurrent()
startAsyncNetworkingStuff() { result in
CFRunLoopStop(runLoop)
}
CFRunLoopRun()
print("end")
exit(EXIT_SUCCESS)
Please don't use ugly while
loops.
Here's how to use URLSession
in a macOS Command Line Tool using Swift 4.2
// Mac Command Line Tool with Async Wait and Exit
import Cocoa
// Store a reference to the current run loop
let runLoop = CFRunLoopGetCurrent()
// Create a background task to load a webpage
let url = URL(string: "http://SuperEasyApps.com")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
print("Loaded: \(data) bytes")
}
// Stop the saved run loop
CFRunLoopStop(runLoop)
}
task.resume()
// Start run loop after work has been started
print("start")
CFRunLoopRun()
print("end") // End will print after the run loop is stopped
// If your command line tool finished return success,
// otherwise return EXIT_FAILURE
exit(EXIT_SUCCESS)
You'll have to call the stop function using a reference to the run loop before you started (as shown above), or using GCD in order to exit as you'd expect.
func stopRunLoop() {
DispatchQueue.main.async {
CFRunLoopStop(CFRunLoopGetCurrent())
}
}
References
https://developer.apple.com/documentation/corefoundation/1542011-cfrunlooprun
Run loops can be run recursively. You can call CFRunLoopRun() from within any run loop callout and create nested run loop activations on the current thread’s call stack.
https://developer.apple.com/documentation/corefoundation/1541796-cfrunloopstop
If the run loop is nested with a callout from one activation starting another activation running, only the innermost activation is exited.
(Answering my own question)
Adding the following snippet to my async network completion code allows "end" to be printed :
DispatchQueue.main.async {
shouldKeepRunning = false
}