Hi,
I'm using SPM to extract the networking component of an app into a package. Part of what I thought I'd get, by doing that, is to be able to build a command-line-tool to make testing quicker. The tool is essentially like a ping command built on top of the network package, that I can use to test the networking logic while I simulator various conditions with the Cloud API (offline, busy, etc).
However, it's the first command line tool I've built. And I'm learning that I need to learn more about command line tool building 🙂
This is a fairly standard method to set the stateUpdateHandler (in my networking package). It works fine in the iOS simulator, but doesn't get called when used in the command line tool. But, I call a .send() after starting the connection and in both cases the data is sent to the remote side ('nc -l 80').
Can anyone recommend some good resources to learn, that will help me figure out why the below doesn't work on the command line?
thanks
func startConnection() {
guard let connection = connection else {
return
}
// A handler that receives connection state updates.
connection.stateUpdateHandler = { newState in
switch newState {
case .setup:
print("setup: \(connection)")
case .preparing:
print("preparing: \(connection)")
case .ready:
print("established: \(connection)")
case .waiting(let error):
print("\(connection) waiting with \(error)")
case .failed(let error):
print("\(connection) failed with \(error)")
default:
break
}
}
connection.start(queue: .main)
}
Without your entire program it is difficult to say. To test it I created a Swift package from the command line:
$ mkdir NetworkCLI
$ cd NetworkCLI
$ swift package init --type executable
$ swift package generate-xcodeproj
Open up the .xcodeproj in Xcode and setup the main.swift file like so (Notice how I park the main thread with the technique that Quinn used):
import Foundation
class Main {
var conn: Connection?
static let shared = Main()
func setup() {
conn = Connection()
conn?.delegate = self
conn?.startConnection()
dispatchMain()
}
func sendData() {
for index in 1...3 {
conn?.send(message: "test message \(index)")
}
}
}
extension Main: ConnectionDelegate {
func connectionDidUpdate(_ conn: Connection, _ ready: Bool) {
if ready {
sendData()
}
}
}
Main.shared.setup()
Now create a connection class and set it up the way you previously had:
import Foundation
import Network
protocol ConnectionDelegate: AnyObject {
func connectionDidUpdate(_ conn: Connection, _ ready: Bool)
}
class Connection: ConnectionDelegate {
var connection: NWConnection?
weak var delegate: ConnectionDelegate?
func startConnection() {
connection = NWConnection(host: "127.0.0.1", port: NWEndpoint.Port("8000")!, using: .tcp)
connection?.stateUpdateHandler = { [weak self] newState in
guard let strongSelf = self else { return }
switch newState {
case .setup:
print("Connection Setup")
case .preparing:
print("Connection Preparing")
case .ready:
print("Connection Established")
strongSelf.delegate?.connectionDidUpdate(strongSelf, true)
case .waiting(let error):
print("Waiting with \(error)")
case .failed(let error):
print("Failed with \(error)")
default:
break
}
}
connection?.start(queue: .main)
print("connection start")
}
func connectionDidUpdate(_ conn: Connection, _ state: Bool) {
print("Did update state to ready: \(state), \(conn)")
}
func send(message: String) {
print("sending '\(message)' while connection state is \(String(describing: connection?.state))")
connection?.send(content: Data(message.utf8), completion: NWConnection.SendCompletion.contentProcessed { error in
if error != nil {
print("Error sending: \(error?.localizedDescription)")
}
})
}
}
On the terminal listen on port 8000 with netcat:
$ nc -l 8000
Now either run your project from Xcode or run the executable from the commandline:
$ ./NetworkCLI
And you should see:
connection start
Connection Preparing
Connection Established
sending 'test message 1' while connection state is Optional(Network.NWConnection.State.ready)
sending 'test message 2' while connection state is Optional(Network.NWConnection.State.ready)
sending 'test message 3' while connection state is Optional(Network.NWConnection.State.ready)
With the listening end contains:
test message 1test message 2test message 3
Matt Eaton
DTS Engineering, CoreOS
meaton3 at apple.com