Dealing with Bluetooth Low Energy (BLE) through Swift can be pretty tricky. The interfaces and delegation are straightforward, but there is an implicit “right way” of doing things that (as far as I could find) is not documented anywhere.

I don’t know if the way you have to hold on to references to things or they get immediately discarded and cleaned up was surprising only for me with zero Swift background, but it took some time to get used to. For example CBPeripheral and CBL2CAPChannel objects require that you keep a reference to them, or the connection will be terminated, resulting in interesting behaviors.

Another thing is that I couldn’t find any details about error patterns in the documentation, but I figured out the following:

  • the input and output streams that come with a CBL2CAPChannel are not opened when you get your hands on them, and result in a pretty obscure 0x0122 failure if you try to use them without opening them first
  • you have to read/write those streams from the same DispatchQueue where they are scheduled, or the operations will silently just do nothing. Logging Thread.current can help debugging this.
  • error 436 means the local (you) severed the connection. This usually happens when you forget to hold on to the CBPeripheral instance
  • error 431 means the remote peer severed the connection. I’m not entirely sure what triggers this, since usually the disconnect is very graceful with a delegate callback. Maybe sometimes closing the streams doesn’t make it in time and this error gets logged first?
  • error 582 means the PSM used to initiate the CBL2CAPChannel is incorrect.