swiftrfcommunsafemutablepointer

How to unwrap UnsafeMutablePointer<BluetoothRFCOMMChannelID> in swift


This seems to be a common issue for beginners in swift. I read many of the cases but I have not found any explanation that addresses my case.

I am using IOBluetooth to open a bluetooth serial connection.

I need to use UnsafeMutablePointer<BluetoothRFCOMMChannelId>

I set it up like this

var rfcommChannel: IOBluetoothRFCOMMChannel? = nil
var rfcommChannelID: BluetoothRFCOMMChannelID? = nil
let channelIdResult = service?.getRFCOMMChannelID(&rfcommChannelID)


if channelIdResult != kIOReturnSuccess {
   print("Failed to get RFCOMM channel ID: \(String(describing: channelIdResult))")
    return
}
        
print(rfcommChannelID as Any) // Optional(2)
_ = device?.openRFCOMMChannelAsync(&rfcommChannel, withChannelID: rfcommChannelID!, delegate: self)
        
        

the application crashes with

 Fatal error: Unexpectedly found nil while unwrapping an Optional value

I have tried

if let rfcommChannelID = rfcommChannelID {
   _ = device?.openRFCOMMChannelAsync(&rfcommChannel, withChannelID: rfcommChannelID, delegate: self)
} else {
   print("failed for some reason")
}

which always fails for some reason. What am I missing? it works if I hard code the id's but that does't really help

I can see that the value is NOT nil in the debugger, yet every test use considers the value nil. Even printing to the console prints optional(2) which is the correct value


Solution

  • This code is not correct:

    var rfcommChannelID: BluetoothRFCOMMChannelID? = nil
    let channelIdResult = service?.getRFCOMMChannelID(&rfcommChannelID)
    

    getRFCOMMChannelID() takes a pointer to a BluetoothRFCOMMChannelID, not a pointer to a BluetoothRFCOMMChannelID?. This type is really just a typealias for UInt8. A UInt8 is not the same size as a UInt8? (the first is 1 bytes; the second is 2 byte). So getRFCOMMChannelID updates the first byte of a two byte value, and corrupts the data structure.

    What you mean is this:

    var rfcommChannelID: BluetoothRFCOMMChannelID = 0
    

    It's correct for rfcommChannel to be Optional, since that's what openRFCOMMChannelAsync accepts.

    Why doesn't the compiler catch this mistake? Honestly, I'm not completely certain. This may be a compiler bug. I'm still pondering if there are situations where it is particularly convenient that this is allowed for compatibility with existing C code. (IOBluetooth has not been nullability-audited for Swift. I'm not sure that matters, but it might be related to the bug.)