0

I'm trying to communicate with CoreBluetooth using Combine, but my completion handler of a PassthroughSubject is not called. Below you can see a rough layout of the code. The DetailViewModel contains the bluetooth peripheral and the data to send.

final class DetailViewModel: NSObject, ObservableObject, CBPeripheralDelegate {
    // Called when the correct write characteristic is found
    private var writeCharacteristicReceived = PassthroughSubject<CBCharacteristic, Never>()
    // Used to send and listen for peripheral data
    private var bluetoothDidChange = PassthroughSubject<Data, Error>()

    func open() -> AnyPublisher<Data, Error> {
        writeCharacteristicReceived.tryMap { characteristic -> AnyPublisher<Data, Error> in
            print("Write char", characteristic)

            let data: Data = try constructPayload()

            self.peripheral?.writeValue(data, for: characteristic, type: .withoutResponse)

            return self.bluetoothDidChange.eraseToAnyPublisher()
        }
        .switchToLatest()
        .eraseToAnyPublisher()
    }

    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        print("Did update value", characteristic.value ?? Data())
        guard let value = characteristic.value, value.count >= 84 else { return }

        defer {
            // Never called
            bluetoothDidChange.send(completion: .finished)
        }

        do {
            let message: Data = try parse(value)
            bluetoothDidChange.send(message)
            // Never called when placed here either
            // bluetoothDidChange.send(completion: .finished)
        } catch {
            bluetoothDidChange.send(completion: .failure(error))
        }
    }
}

I then listen for these changes in the view itself as follows

viewModel.open().sink(receiveCompletion: { (completion) in
    print("Open completion: \(completion)")
}, receiveValue: { (payload) in
    print("Open payload \(payload)")
}).store(in: &cancellable)

Now, this works fine for receiving values every now and then and the completion block is correctly called when an error occurs. But I never get the finished completion hander, not even when I specifically do send(completion: .finished). Can anyone help me out?

Bram
  • 2,718
  • 1
  • 22
  • 43

1 Answers1

0

The issue related to the writeCharacteristicReceived.send(completion: .finished) never being called. Adding that to the function called by the delegate resolved the issue.

func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
    if let error = error {
        writeCharacteristicDiscovered.send(completion: .failure(error))
    }
    writeCharacteristicDiscovered.send(characteristic)
    writeCharacteristicDiscovered.send(completion: .finished)
}
Bram
  • 2,718
  • 1
  • 22
  • 43
  • 1
    _"...at the correct location"_, please complete your answer by including the code where this was needed to help others looking for a similar solution. – Joakim Danielson Feb 18 '21 at 13:19