Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to exit a runloop?

Tags:

swift

swift3

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?

like image 486
hotpaw2 Avatar asked May 06 '17 20:05

hotpaw2


People also ask

How do you stop a run loop?

You can run your runloop with CFRunLoopRun() and stop it with CFRunLoopStop(). Or you can use one of the date-oriented calls (like -runUntilDate:) and poll to see if the speech synthesis is done.

What is iOS RunLoop?

Overview. A RunLoop object processes input for sources, such as mouse and keyboard events from the window system and Port objects. A RunLoop object also processes Timer events. Your application neither creates nor explicitly manages RunLoop objects.

What RunLoop Main?

The RunLoop. main uses several modes and switches to a non-default mode when user interaction occurs. However, RunLoop. main as a Combine scheduler only executes when the default mode is active. In other words, the mode is switched back to default when user interaction ends and the Combine closure executes.

What is Uikit RunLoop?

Role of a run loop On iOS, a run loop can be attached to a NSThread . Its role is to ensure that its NSThread is busy when there is work to do and at rest when there is none. The main thread automatically launches its run loop at the application launch.


3 Answers

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.


Update:

In Swift 5.5+ with async/await it has become much more comfortable. There's no need anymore to maintain the run loop.

Rename the file main.swift as something else and use the @main attribute like in a normal application.

@main
struct CLI {

    static func main() async throws {
        let result = await startAsyncNetworkingStuff()
        // do something with result 
    }
}

The name of the struct is arbitrary, the static function main is mandatory and is the entry point.

like image 137
vadian Avatar answered Oct 09 '22 01:10

vadian


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.

like image 4
Paul Solt Avatar answered Oct 09 '22 02:10

Paul Solt


(Answering my own question)

Adding the following snippet to my async network completion code allows "end" to be printed :

DispatchQueue.main.async {
    shouldKeepRunning = false
}
like image 1
hotpaw2 Avatar answered Oct 09 '22 00:10

hotpaw2