0

Now facing some challenges using CoreBlueTooth L2CAP channel. In order to better understand how things work. I have taken the L2CapDemo (master) (https://github.com/paulw11/L2CapDemo) from GitHub and tried to experiment with it. Here is what I have done, along with one question.

In have replaced the sendTextTapped function, with this one :

@IBAction func sendTextTapped(_ sender: UIButton) {
    guard let ostream = self.channel?.outputStream else {
        return
    }

    var lngStr = "1234567890"
    for _ in 1...10 {lngStr = lngStr + lngStr}
    let data = lngStr.data(using: .utf8)!

    let bytesWritten =  data.withUnsafeBytes { ostream.write($0, maxLength: data.count) }
    print("bytesWritten = \(bytesWritten)")
    print("WR = \(bytesWritten) / \(data.count)")
}

And the execution result is:

bytesWritten = 8192
WR = 8192 / 10240

That allows me to see what happens in the case where bytesWritten < data.count. In other words, all the bytes cannot be sent over in one chunk.

Now comes the question. The problem is I see nothing, the bytes left over seems to be just ignored. I want to know what to do if I do not want to ignore those bytes. What is the way to care about the rest of the bytes? There will be cases where we will need to transfer tens of thousands or even hundreds of thousands of bytes.

Michel
  • 10,303
  • 17
  • 82
  • 179

1 Answers1

2

You simply need to make a note of how many characters were sent, remove those from the data instance and then when you get a delegate callback indicating space is available in the output stream, send some more.

For example, you could add a couple of properties to hold the queued data and a serial dispatch queue to ensure thread-safe access to that queue:

private var queueQueue = DispatchQueue(label: "queue queue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem, target: nil)

private var outputData = Data()

Now, in the sendTextTapped function you can just add the new data to the output queue:

@IBAction func sendTextTapped(_ sender: UIButton) {     
    var lngStr = "1234567890"
    for _ in 1...10 {lngStr = lngStr + lngStr}
    let data = lngStr.data(using: .utf8)!

    self.queue(data:data)  
}

the queue(data:) function adds the data to the outputData object in a thread-safe manner and calls send()

private func queue(data: Data) {
    queueQueue.sync  {
        self.outputData.append(data)
    }
    self.send()   
}

send() ensures that the stream is connected, there is data to send and there is space available in the output stream. If all is OK then it sends as many bytes as it can. The sent bytes are then removed from output data (again in a thread safe manner).

private func send() {

    guard let ostream = self.channel?.outputStream, !self.outputData.isEmpty, ostream.hasSpaceAvailable  else{
        return
    }
    let bytesWritten =  outputData.withUnsafeBytes { ostream.write($0, maxLength: self.outputData.count) }
    print("bytesWritten = \(bytesWritten)")
    queueQueue.sync {
        if bytesWritten < outputData.count {
            outputData = outputData.advanced(by: bytesWritten)
        } else {
            outputData.removeAll()
        }
    }
}

The final change is to call send() in response to a .hasSpaceAvailable stream event:

func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
    switch eventCode {
    case Stream.Event.openCompleted:
        print("Stream is open")
    case Stream.Event.endEncountered:
        print("End Encountered")
    case Stream.Event.hasBytesAvailable:
        print("Bytes are available")
    case Stream.Event.hasSpaceAvailable:
        print("Space is available")
        self.send()
    case Stream.Event.errorOccurred:
        print("Stream error")
    default:
        print("Unknown stream event")
    }
}

You can see the modified code in the largedata branch of the example

Paulw11
  • 108,386
  • 14
  • 159
  • 186
  • OK fair enough. It has to be handled by hand then. The algorithmic part of it is rather easy, but I just thought there might be some processing automatically handled that I was not aware of. Thanks for making this clear. Beside, since I am eventually interested in transferring binary data (and not only strings), I will later have to see how to remove bytes (and add them on the receiving side) from a Data object (for which I am not yet sure of the correct syntax to use). – Michel Sep 02 '19 at 08:05
  • The code above will send any data; There is nothing special about binary data vs strings. What you do need to consider is framing - how do you know this is the start of a "file" in the stream and this is the "end' - You probably need to send some sort of header, – Paulw11 Sep 02 '19 at 08:12
  • OK. Great, I will spend some time to look into that. – Michel Sep 02 '19 at 13:47