How to return a value on successful BT connection?

Hey everybody,
I would like to ask for some help with the concurrency with the CoreBluetooth delegates.

I currently have this (non-async) connect-function, that does not return the success of the connection attempt:

    public func connect(device: CBPeripheral) {
        logger.log(type: .Info, "Connection attempt initiated")
        
        guard generalState != STATE.CONNECTED else {
            logger.log(type: .Warning, "Already connected")
            return
        }
        centralManager.connect(device, options: nil)
    }

The centralManager.connect()-function triggers the didConnect() or didFailToConnect()-delegate methods.

Currently in those methods I just set a state, but I don`t know how to pass it back to the connect()-function.

Example didConnect()

    public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        // Set State
        generalState = STATE.CONNECTED
        connectedTracelet = peripheral
        // Discover UART Service
        peripheral.discoverServices([UUIDs.NordicUARTService]) 
    }

How can i get the connection results back as a bool, so that i can use the connect() function like this:

let connected = await connect(device)

Any help would be appreciated.

You use continuations to communicate between the non-async and async worlds. In your connect method, you'd create a continuation and store it in a property:

private var connectContinuation: CheckedContinuation<Void, any Error>? = nil

func connect(device: CBPeripheral) async throws {
    // …
    try await withCheckedThrowingContinuation { cont in
        self.connectContinuation = cont
        centralManager.connect(device, options: nil)
}

Then, in your delegate callback, you can resume the continuation, which will resume the suspended connect call:

public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
    // …
    if let cont = connectContinuation {
        cont.resume()
        self.connectContinuation = nil
    }
}

You should also implement the didFailWithError delegate callback and resume the continuation by throwing the error.

Note that you are responsible for making sure that the continuation is resumed exactly once. If your program can have multiple in-flight connection attempts at the same time, you may need to store multiple continuations and keep track which continuation belongs to which request.

2 Likes

Thank you so much, this approach was what I was missing.
I see now, I need to look deeper into continuations.
Currently for example, I don`t understand how I can pass the errors around. With you example I get:

Cannot convert value of type '(CheckedContinuation<Void, any Error>) -> Void' to expected argument type '(CheckedContinuation<Void, Never>) -> Void'

My mistake, sorry. You have to use withCheckedThrowingContinuation, not withCheckedContinuation. I fixed it in my first post. There may be other mistakes in my code (written without the help of the compiler).