Integrating NWCAT into SwiftUI

I have the Swift version of nwcat ( eskimo's post ) working just fine as a CommandLine version and talking to my SwiftNIO server running on a Raspberry Pi 4 using JSON in both directions. So far, so good, but now I need to integrate this code into my macOS SwiftUI App (which is ready to go).

Despite several weeks trying this and that, I'm failing to understand how to implement this, so I'm hoping someone has some example code that I could learn from.

Please could someone help me out, before I throw my iMac out the window?

Replies

So far, so good, but now I need to integrate this code into my macOS SwiftUI App (which is ready to go).

If I am understanding you correctly, you are wanting to integrate the client side NWConnection code from Quinn's post into your macOS SwiftUI app, is that correct? If so, the first thing I would do is create a managing class to run all of your standard network operations, i.e., setting up the connection, reading data, writing data, handling state updates, and cancelling you connection. From there you will need a way to delegate the communication between your SwiftUI code and your network manager class. This is needed so that when your apps wants to setup a connection and send some data on it, your SwiftUI code can tell your network manager to run these functions, and then that data can be piped back to your view for display. To do this, I will leave it up to you on how you want to create this binding layer between your view and your network manager class.

Please let me know if you have issues implementing the network manager side of this.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
  • You could use an alternative to nwcat code. There are some good modern networking Swift packages on github, such as Alamofire and Networking from freshOS that may be of help, including the JSON decoding.

Add a Comment

I need direction (or an example) for the 3 questions in the following snippet. Please can you help?

import Foundation
import NIO
import NIOFoundationCompat

class NetworkService {
    var connected = false

// Q1 Are these declarations correct?
    var group: MultiThreadedEventLoopGroup
    var bootstrap: ClientBootstrap
    var channel: Channel?

   
    init() {
        self.group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
        self.bootstrap = ClientBootstrap(group: group)
            .channelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
            .channelInitializer { channel in
                channel.pipeline.addHandler(ResponseHandler())
            }
        defer {
            try! self.group.syncShutdownGracefully()
        }
    }

    public func connect(host: String, port: Int, completion: (_ success:Bool, _ error:String) -> Void) {
        if connected {
            completion(false, "ERROR: already connected")
            return
        }
        do {
// Q2 How to change to the correct thread - and return to main?
            channel = try bootstrap.connect(host: host, port: port).wait()
        } catch {
            completion(false, "ERROR: NetworkService.connect() failed to connect")
            connected = false
            return
        }
        connected = true
        completion(true, "")
    }

    public func disconnect(completion: (_ success:Bool, _ error:String) -> Void) {
        if !connected {
            completion(false, "ERROR: already disconnected")
            return
        }
        do {
// Q3 How to change to the correct thread - and return to main?
            try channel?.close().wait()
        } catch {
            completion(false, "ERROR: NetworkService.disconnect() failed to disconnect")
            return
        }
        connected = false
        completion(true, "")
    }

    //
    // more follows...
    //

Of course, any other suggestions will be most appreciated. Thank you.

Regarding your questions, I am going to recommend taking a look at some of the examples from SwiftNIO, as it looks like you may have based your implementation above on either NIOChatClient or NIOEchoClient. Now, I will provide you initial instruction here, but for any additional help with the internals of SwiftNIO it would be best to open an issue on their repo or ask for additional clarification in the Swift Forums with the SwiftNIO tag. If you do open an issue on the SwiftNIO repo, then please follow up here the the issue number.

As for your questions:

// Q1 Are these declarations correct?

Honestly, this is hard to say one way or another without the entire picture, but if you compare it against the samples I mentioned above, then yes, this should at least allow you to get an TCP connection off the ground via IP. If you are having issues reading and checking the state of your connection make sure the ChannelInboundHandler functions are implemented, so this functionality is available to your connection.

Regarding:

// Q2 How to change to the correct thread - and return to main?

// Q3 How to change to the correct thread - and return to main?

First, unless you have specified in your code that this entire class is operating on a separate thread, it would be helpful for you to get a quick sanity check to know if this is already the main or not. Having said that, one approach is to create a delegate pipeline that sends updates back to your ViewModel here to they can eventually make it to the UI. You could define specific functions that deal with connection state, reading / writing of data on your connection, or you could just use one function to handle all updates, and then dispatch these updates to the main.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com

Thank you for this Mathew. I very much appreciate your time.

I did go ahead and posted to the Swift forum. In fact, I've been receiving wonderful assistance from Cory Benfiled during the past few hours.

Here's the link to issue 50173 where I suggest anyone watching should divert their attention.

Post not yet marked as solved Up vote reply of EA7K Down vote reply of EA7K