NWConnection -no Echo from connection.send(content: data, completion : .idempotent)

I'm using peer-to-peer and I successfully connect one client to another but when I send the echo to the individual connection the receiving client doesn't get a response.

Initial send
Code Block
let data = NYKeyedArchiver....
let message = NWProtocolWebSocket.Metadata(opcode: .text)
let context = NWConnection.ContentContext(identifier: "send",
                                                  metadata: [message])
connection.send(content: data, contentContext: context, isComplete: true, completion: .contentProcessed({ (error) in
if let error = error { return }
print("Sent")
}))


Receive data and send Echo response

Code Block
func receivedIncoming(connection) {
connection.receive(minimumIncompleteLength: 1, maximumLength: 65535) { (data, context, isComplete, error) in
if let err = error {
print(err) // never gets hit
return
}
if let data = data, !data.isEmpty {
// do something with data
if let color = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? UIColor {
// this should only run on the other device once echo is received after 5 secs
        self?.view.backgroundColor = color
}
let randomColor = UIColor.random // func create random color
       let colorData = randomColor.encode() // func encode color to data
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
connection.send(content: colorData, completion : .idempotent)
// I've also tried
let message = NWProtocolWebSocket.Metadata(opcode: .text)
           let context = NWConnection.ContentContext(identifier: "send", metadata: [message])
connection.send(content: colorData, contentContext: context, isComplete: true, completion: .contentProcessed({ (error) in
if let error = error { return }
print("Color data sent") // this always prints
}))
}
} else {
print("data is empty") // never gets hit
}
}
}


NWConnection
Code Block
weak var delegate: PeerConnectionDelegate?
var connection: NWConnection?
// Outgoing Connection
init(endPoint: NWEndpoint, delegate: PeerConnectionDelegate) {
self.delegate = delegate
let tcpOptions = NWProtocolTCP.Options()
tcpOptions.enableKeepalive = true
tcpOptions.keepaliveIdle = 2
let parameters = NWParameters(tls: nil, tcp: tcpOptions)
parameters.includePeerToPeer = true
parameters.allowLocalEndpointReuse = true
connection = NWConnection(to: endPoint, using: parameters)
    startOutgoingConnection()
}
// Incoming Connection
init(connection: NWConnection, delegate: PeerConnectionDelegate) {
self.delegate = delegate
self.connection = connection
startIncomingConnection()
}
func startIncomingConnection() {
connection?.stateUpdateHandler = { (nwConnectionState) in
case .ready:
        self.delegate?.receivedIncoming(connection)
// ...
}


Why is the echo data being sent but not received?

Replies

I forgot to add the listener code

Code Block
let tcpOptions = NWProtocolTCP.Options()
tcpOptions.enableKeepalive = true
tcpOptions.keepaliveIdle = 2
let parameters = NWParameters(tls: nil, tcp: tcpOptions)
parameters.includePeerToPeer = true
parameters.allowLocalEndpointReuse = true

The code you posted includes a lot of log points. Can you clarify which ones get hit and which ones don’t.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
@eskimo Thanks for the reply.

Here are the successful print statements:

The Initial Send does not send a color, it just sends some data like a String message :
Code Block
print("Sent") // successful

After the Initial Send sends the message and it is first received, I didn't add a print statement for a successful reception but the code inside Receive data and send Echo response is successful, specifically
Code Block
if let data = data, !data.isEmpty { // successful only once }

Inside there, the data that is decoded, only has the String message from above, the color isn't sent yet so the colorData decoding is skipped. However once the String message is received, I send an echo with the colorData

Code Block
connection.send(content: colorData, ... { }


Inside the completionHandler of the echo, this print statement always runs:

Code Block
print("Color data sent") // successful


At that point the other device, the Receive Data receives nothing, there isn't a response whatsoever. Nothing prints, it's as if the connection got cut off after the first reception. I put breakpoints everywhere but none of them get hit.

At that point the other device, the Receive Data receives nothing,
there isn't a response whatsoever.

So the other device is running the same code? Was its connection created using this code:

Code Block
// Outgoing Connection
init(endPoint: NWEndpoint, delegate: PeerConnectionDelegate) {
startOutgoingConnection()
}


If so, what does startOutgoingConnection look like?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
Yes, they both have the same exact code. Btw neither device receives the echo but they both receive that first message.

Here is startOutgoingConnection, actually here is PeerConnection because it is 1 class that has 2 initializers, one for outgoing and one for incoming. I put break points everywhere but nada on that 2nd incoming message.

Code Block
protocol PeerConnectionDelegate: class {
    func sendOutgoing(_ connection: NWConnection)
    func receivedIncoming(_ connection: NWConnection)
}
class PeerConnection: Hashable {
    static func == (lhs: PeerConnection, rhs: PeerConnection) -> Bool {
        return lhs.connection?.endpoint.debugDescription == rhs.connection?.endpoint.debugDescription
    }
    func hash(into hasher: inout Hasher) {
        guard let connection = connection else { return }
        hasher.combine(connection.endpoint.debugDescription)
    }
    weak var delegate: PeerConnectionDelegate?
    public var connection: NWConnection?
// Outgoing
    init(endPoint: NWEndpoint, delegate: PeerConnectionDelegate) {
        self.delegate = delegate
        let tcpOptions = NWProtocolTCP.Options()
        tcpOptions.enableKeepalive = true
        tcpOptions.keepaliveIdle = 2
        let parameters = NWParameters(tls: nil, tcp: tcpOptions)
        parameters.includePeerToPeer = true
        parameters.allowLocalEndpointReuse = true
        connection = NWConnection(to: endPoint, using: parameters)
        startOutgoingConnection()
    }
// Incoming
    init(connection: NWConnection, delegate: PeerConnectionDelegate) {
        self.delegate = delegate
        self.connection = connection
        startIncomingConnection()
    }
    func startIncomingConnection() {
        guard let connection = connection else { return }
        connection.stateUpdateHandler = { [weak self](nwConnectionState) in
            switch nwConnectionState {
            case .preparing: print("preparing")
            case .setup: print("setting up")
            case .waiting(let error): print("\(error.localizedDescription)")
            case .ready:
                self?.delegate?.receivedIncoming(connection)
            case .cancelled:
                connection.cancel()
// remove incoming connection from an array that holds it/them and remove corresponding cell
            case .failed(let error):
                connection.cancel()
                // remove incoming connection from an array that holds it/them and remove corresponding cell
            default:break
            }
        }
        connection.start(queue: .main)
    }
    func startOutgoingConnection() {
        guard let connection = connection else { return }
        connection.stateUpdateHandler = { [weak self](nwConnectionState) in
            switch nwConnectionState {
            case .preparing: print("preparing")
            case .setup: print("setting up")
            case .waiting(let error): print(error.localizedDescription)
            case .ready:
                self?.delegate?.sendOutgoing(connection)
            case .cancelled:
                connection.cancel()
// remove outgoing connection from an array that holds it/them
            case .failed(let error):
                connection.cancel()
// remove outgoing connection from an array that holds it/them
            default:break
            }
        }
        connection.start(queue: .main)
    }
}


The connection isn't failing on either end/device because once it is cancelled I remove data from both devices which never happens after they both send and receive messages from each other. For example Device_A sends its info to Device_B and vice versa. I have a cell that shows the connection info String data like connection.endpoint.debugDescription and the uid from the opposite device. Once either device goes to the background and the connection is cancelled then both devices remove the corresponding cell.

If the connection was cancelled after the initial send/receive then both cells (the cell on each device) would immediately disappear.

Yes, they both have the same exact code.

OK, then in that case your problem is with your receivedIncoming(:_) method. In Network framework you have ask for data. You do that initially by calling receivedIncoming(:_) when the connection goes to the .ready state [1]. However, once you’ve received some data you never ask for more, and so you never receive any more.

A typical pattern is something like this:

Code Block
final class MyConnection {
let connection: NWConnection
… lot o’ code …
func receiveIncoming() {
self.connection.receiveMessage { (dataQ, _, isDone, errorQ) in
// … handle the message, returning early if there’s a
// problem but if all went well then …
self.receiveIncoming()
}
}
}


Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Note that you don’t need to wait for the .ready state. I generally start the receive process when I start the connection. The read request will lie dormant waiting for the the connection to become ready.
I was logged in, typed a message with all of my code, described 3 new problems based on your answer, pressed Submit, and the forum message said I was unauthorized. I had to log back in and I lost everything that I just typed

This isn't the first time this has happened to me. Apple needs to fix this issue. The same exact thing has happened to me before in AppStoreConnect when responding to the AppReview team in the Resolution Center. I typed an entire message and it said I wasn't logged in even though I was logged in and then I lost everything I typed.

Oh man I'm so sick right now.

I apologize but I have to send the message again later.
I changed my PeerConnection class to follow the code the way that you did but it's not working correctly. There are 3 issues
  • 1st issue, the echoData is always sent, however inside the PeerConnection class, connection.receive(minimumIncompleteLength.., inside if let data = data, !data.isEmpty { } I call receivedIncoming() again but the returned echoData is nil.

  • 2nd issue, the only way I can get it to keep listening is if I move receivedIncoming() at the very top of the completionHandler like below but the echoData that comes back is is still nil

Code Block
connection.receive(minimumIncompleteLength.. { ...
self?.receivedIncoming()
if let err = error { ... }
if let data = data, !data.isEmpty {
// ...
} else {
print("=-=-=-=-= Receive data is nil -=-=-=-=-=") // returned echoData is always nil
}
}

  • 3rd issue, inside the same connection.receive(minimumIncompleteLength.., if there are any problems I remove any connections via delegate?.removeIncomingConnection(connection), the ViewController class that conforms to it will remove the connection thus removing the encodedData (a cell shows the data). This is a separate issue relating more so to my other question. But the fact that the returned echoData is nil is causing collateral damage


Coded like your example:

Code Block
protocol PeerConnectionDelegate: class {
    func sendOutgoing(_ connection: NWConnection)
    func removeIncomingConnection(_ connection: NWConnection)
    func readDataFromIncomingConnection(_ data: Data, _ connection: NWConnection)
}
final class PeerConnection: Hashable {
// outgoing init is the same
// incoming init is the same
func startIncomingConnection() {
connection.stateUpdateHandler = { [weak self](nwConnectionState) in
case .ready:
self?.receivedIncoming()
}
func receivedIncoming() {
        guard let connection = connection else { return }
        connection.receive(minimumIncompleteLength: 1, maximumLength: 65535) { [weak self](data, context, isComplete, error) in
            if let err = error {
                print("received error: (err.localizedDescription)")
                self?.delegate?.removeIncomingConnection(connection)
                return
            }
            if let data = data, !data.isEmpty {
                let echoString = String(decoding: data, as : UTF8.self)
                if echoString == "12345" {
                    print("echo received, stop listening")
                    return
                }
self?.delegate?.readDataFromIncomingConnection(data, connection) // vc conforms to this
                self?.receivedIncoming()
            } else {
                print("=-=-=-=-= Receive data is nil -=-=-=-=-=") // returned echoData gets hit here
                self?.delegate?.removeIncomingConnection(connection)
            }
        }
    }
}
extension ViewController: PeerConnectionDelegate {
// ... other peerConnection Delegate methods
func readDataFromIncomingConnection(_ data: Data, _ connection: NWConnection) {
guard let decodedData = NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) ... else { return }
// display decodedData inside a cell
// first make sure connection isn't already in the array, the arrOfConnections is an ivar
arrOfConnections.append(connection)
let echoData = "12345".data(using: String.Encoding.utf8)
        let message = NWProtocolWebSocket.Metadata(opcode: .text)
let context = NWConnection.ContentContext(identifier: "send", metadata: [message])
connection.send(content: echoData, contentContext: context, isComplete: true, completion: .contentProcessed({
(error) in
          if let error = error { return }
print("echoData successfully sent") // this always prints
guard let echoData = echoData else { return }
let echoString = String(decoding: echoData, as : UTF8.self)
if echoString == "12345" {
              print("here is the echoData that was sent: \(backToString)") // always prints
}
    }))
}