CFRunLoop in Swift Command Line Program
Asked Answered
B

2

10

I am writing a command line application in Swift using a third-party framework that (if I understand the code correctly) relies on GCD callbacks to complete certain actions when a socket receives data. In order to better understand the framework, I have been playing around with a sample Cocoa application the framework's author wrote to go along with the framework.

Because the sample application is a Cocoa application, the run loops are handled automatically. I'm including snippets of code from the sample application (MIT license) to give an idea of how it works:

class AppDelegate: NSObject, NSApplicationDelegate {


  var httpd : Connect!

  func startServer() {
    httpd = Connect()
      .onLog {
        [weak self] in // unowned makes this crash
        self!.log($0)
      }
      .useQueue(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0))

...

     httpd.listen(1337)
}

...

 func applicationDidFinishLaunching(aNotification: NSNotification?) {
    startServer()

   ...

    }
  }

I'd like to modify the sample application to run from the command line. When I put the startServer() function into a command line application, it runs, but the socket is immediately closed after it is opened, and the program finishes executing with an exit code 0. This is expected behavior, as there are no run loops in an Xcode command line project, and thus the program doesn't know to wait for the socket to receive data.

I believe the correct way to get the socket to stay open and the program to continuously run would be to put the main thread in a CFRunLoop. I have looked over Apple's documentation and, except for the basic API reference, there is nothing on threading in Swift. I have looked at third party resources, but they all involve alternate threads in iOS and Cocoa applications. How do I properly implement a CFRunLoop for the main thread?

Bedeck answered 4/8, 2014 at 19:54 Comment(1)
My apologies for chiming in on such an old question, but for the sake of future readers, it’s worth noting that the pattern here, namely starting a runloop, is not necessary in Swift concurrency code with async-await. You need it with asynchronous completion handler or delegate patterns, but not with async-await.Cushiony
B
8

It seems like Martin R's answer should work, however I was able to get the socket to stay open with a single function call. At the end of the startServer() function, I put the line:

CFRunLoopRun()

Which worked.

Bedeck answered 4/8, 2014 at 20:25 Comment(2)
How do you end the run loop after calling this function?Mayamayakovski
I figured it out: CFRunLoopStop(CFRunLoopGetMain())Mayamayakovski
K
7

The NSRunLoop Class Reference has an example for a simple runloop:

BOOL shouldKeepRunning = YES;        // global

NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

which can be translated to Swift:

var shouldKeepRunning = true        // global

let theRL = NSRunLoop.currentRunLoop()
while shouldKeepRunning && theRL.runMode(NSDefaultRunLoopMode, beforeDate: NSDate.distantFuture()) { }

Alternatively, it might be sufficient to just call

dispatch_main()

Update for Swift 3.1:

let theRL = RunLoop.current
while shouldKeepRunning && theRL.run(mode: .defaultRunLoopMode, before: .distantFuture) { }

or

dispatchMain()
Knish answered 4/8, 2014 at 20:20 Comment(1)
In fact RunLoop.current() is now a var so it should be RunLoop.currentViridis

© 2022 - 2024 — McMap. All rights reserved.