1

I'm converting a Swift MacOS command-line tool/daemon to use Swift-NIO for networking. This is my first Swift-NIO project.

The tool fires a timer every 0.1 second. Here's the line at the bottom of main.swift which fires up the daemon/runloop, prior to the Swift-NIO conversion:

RunLoop.current.run()

Here's the timer in my Universe.swift class init(). There is always exactly one instance of this class:

    timer = Timer(timeInterval: 1.0 / updatesPerSecond, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
    timer?.tolerance = 0.3 / updatesPerSecond
    debugPrint("Timer initialized")
    if let timer = timer {
        RunLoop.current.add(timer, forMode: RunLoop.Mode.common)
    }

In this configuration the timer fires 10 times per second as expected. But if I get any network input my Swift-NIO library crashes because its not in the expected event loop.

enter image description here In Swift-NIO, I'm supposed to add a channel.closeFuture.wait() line to the bottom of my main.swift:

// This will never unblock as we don't close the ServerChannel.
try channel.closeFuture.wait()
RunLoop.current.run()

That solves the Swift-NIO crash, but then I never get to my timer RunLoop, so my timer doesn't fire.

How can I use Swift-NIO to receive (and send) network data, while still having a timer running?

If it helps, the full open source for this project is at https://github.com/darrellroot/netrek-server-swift.

Darrell Root
  • 724
  • 8
  • 19
  • 1
    The SwiftNIO crash you saw is missing some context. Can you provide the code that you’re using in response to the timer firing (the `timerFired` selector)? This is likely violating a threading requirement. – Lukasa Jul 09 '20 at 07:21

1 Answers1

0

Lukasa was right. I was missing (and not understanding) some important context.

My timer was eventually trying to send data using SwiftNIO like so:

    if let context = context {
        let buffer = context.channel.allocator.buffer(bytes: data)
        _ = context.channel.writeAndFlush(buffer)
    }

The fix was to "dispatch" the sending of the traffic to the context-related EventLoop:

    if let context = context {
        context.eventLoop.execute {
                let buffer = context.channel.allocator.buffer(bytes: data)
                _ = context.channel.writeAndFlush(buffer)
        }
    }

It feels very similar to DispatchQueue.main.async { } for updating the GUI in iOS apps, but with different Swift-NIO related terminology.

Darrell Root
  • 724
  • 8
  • 19