Dec 21, 2016

How to Get a Response from a CoreBluetooth Write

You write to a Bluetooth device in CoreBluetooth using the CBPeripheral method writeValue(_:for:type:). You can ask the peripheral for confirmation by indicating the type, which is either .withResponse or .withoutResponse. If you pass .withResponse, how do you get that response?

You will be notified in a delegate callback peripheral(_:didWriteValueFor:error:) This method is passed a CBPeripheral, a CBCharacteristic, and an Error?. The response indicates one of two things:

  1. An error occured, indicated by an Error passed in the error parameter.
  2. The write was successful, indicated by a nil error.

That's it. Don't expect any aditional messages as part of the response. The delegate is passed a CBCharacteristic, which has a value field. Maybe that contains more info? Actually, no. That contains the last read value of the characteristic. It won't indicate the just written new value, even if the delegate receives confirmation that the write was a success.

If you want the new value of the characteristic, you can read it with readValue(for:). If you have subscribed to notifications with setNotifyValue(_:for:), you will automatically get a callback when the value changes. Either way, you wont't get an automatic notification of the new value as part of the write confirmation response.

Dec 12, 2016

Why You Can't Assign nil to Just Anything in Swift

Swift takes a strong stance on which types can be assigned nil. For example, this is an error:

var i = 1
i = nil //error: nil cannot be assigned to type 'Int'
// It is also an error for objects:
var obj = NSObject()
obj = nil //error: nil cannot be assigned to type 'NSObject'

Many languages struggle to express the notion of absence of value. The problem is that it is often implemented in a way that makes it indistinguishable from the integer 0. It is also often easy to mistake for a pointer. Objective-C suffered from this, and there were several ways to indictate an item has no value: nil, NULL, NSNull. For scalar values, something like NSIntegerMax was often used.

Swift has a single way to express absence of value for all types. Conversely, nil indicates the absence of a value and nothing else. The Optional type is the canonical way to express this absence. It is impossible to mistake a nil object with the number zero, or for a pointer to memory. You will never question whether a value is nil or really 0.

This strict separation between value and absense-of-value means you can't assign nil just to anything. The literal nil is assignable only to types which implement the protocol ExpressibleByNilLiteral. And there is exactly one type in the standard library that implements that protocol: the Optional enumeration. In other words, you can assign the value nil only to Optionals.1 Because nil is not a number, you can't assign it to an Int.

An Optional is generic, so you can have Optional<Int>, an Optional<Double>, an Optional<NSObject> and so on. The Optional is the type that can take a nil assignment. An Optional<Int> either has no value, or it has an intenger value, possibly 0.

If you have an instance that might lack any meaningful value in its lifetime, declare it as an optional. When it has no meaningful value, assign it nil.

var i: Int? = 1 // same as var i: Optional<Int> = 1
var j: Int? // uninitialized optionals are implicitly nil
i = nil // Optional accepts nil assignment
var obj: NSObject? = NSObject()
obj = nil // Optional accepts nil assignment

Also note that these are three very diffent values:

let zeroValue: Int = 0
let nilOptional: Int? = nil
let zeroOptional: Int? = 0

zeroValue == 0      // true
zeroValue == nil    // false
nilOptional == 0    // false
nilOptional == nil  // true
zeroOptional == 0   // true
zeroOptional == nil // false

If you ever get a compiler error about assigning nil to a non-optional, this is why. The benefit is that if you ever do encounter a nil optional, you won't have to second-guess whether the value is meant to indicate absence of value. This is true whether it it an object you created, or whether you received it from another library.


  1. Implementing ExpressibleByNilLiteral on any other type is explicitly discouraged by Apple, and would break the semantic meaning of nil. Don't do that. 

Dec 05, 2016

Read From A Bluetooth LE Device with CoreBluetooth

Apple's CoreBluetooth Programming Guide is pretty good, but it can be overwhelming if you're diving into Bluetooth LE (BLE) for the first time. One potential point of confusion is that some objects are needed to connect to another device, and other objects are needed to turn your phone into a BLE device. I'm am not going to cover all the things you can do with CoreBluetooth, but just follow one common use case: connecting to a device and querying it for the values of whatever properties (or characteristics) it keeps.

Here the overview: a CBCentralManager is queried to discover nearby devices. Each found device is presented as an instance of CBPeripheral. Peripherals are then queried to find available services, which are returned as instances of CBService. A service is queried to find available characataristics, presented as CBCharacteristic. Finally, with your characteristics, you can query the peripheral to read the value.

It's not that hard once you've done it. The main difficulty is that you have to make sure you're listening on the right delegate method for the object you just messaged. Just keeping the right method pairs in mind during this extended call-and-response is most of the battle. Here's how it happens in detail:

First I create a CBCentralManager. I store it in an instance variable because there will be a lot of back-and-forth delegate calls.

self.central = CBCentralManager(delegate: self, queue: nil)
// Passing nil to queue: uses the main queue. Use a background
// queue in your shipping app

There is one required delegate method to implement: centralManagerDidUpdateState(_ central: CBCentralManager). I'll implement others too, but first the required method:

func centralManagerDidUpdateState(_ central: CBCentralManager) {
    switch central.state {
    case .poweredOn:
        central.scanForPeripherals(withServices: nil, options: nil)
    default:
        print("Bluetooth couldn't be powered on and I'm too " +
              "lazy to do prooper error handling")
    }
}

Here I'm told the device is powered on. I'm ready to start scanning with central.scanForPeripherals(withServices: nil, options: nil). Notice that I provide nil here for the list of services. For a shipping app this is frowned upon becaue it is power intensive. While I'm poking around I'm going to ask for all the data I can get.

One I ask for devices, the CBCentralManager responds with a delegate callback.

    func centralManager(_ central: CBCentralManager,
           didDiscover peripheral: CBPeripheral,
                advertisementData: [String : Any],
                        rssi RSSI: NSNumber) {

        self.peripiherals.append(peripheral)
    }

I get a callback for each peripheral, containing an instance of CBPeripiheral, along with some data: the RSSI and the advertisement data. I'm going to ignore the extra information and just append the peripheral to an array.

Now I want to connect to my peripheral. I'll pick one and use the CBCentralManager to connect to the peripheral.

let peripheral = peripherals[index]
self.centralManager.connect(peripheral options: nil)

Again I await notification via a callback from the CBCentralManager. Here's my CBCentralManagerDelegate method:

func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {

        // Assume I have a peripheralDelegate
        // Maybe it's my view controller
        peripheral.delegate = peripheralDelegate
        central.stopScan() // Be kind to your battery.
        peripheral.discoverServices(nil)
}

Once the peripheral connects, I discover all the service by passing nil to discoverServices(). Again, I can save power by asking only for the services I need. My current quest for knowledge outweighs the need for power efficiency.

I wait to hear back from a delegate call, this time not from the CBCentralManager, but from the CBPeripheral1.

Here's my delegate method from CBPeripheralDelegate for discovered services. Notice that I am not passed the discovered services directly, but they are in a property on the peripheral.

func peripheral(_ peripheral: CBPeripheral,
                didDiscoverServices error: Error?) {
    guard let services = peripheral.services else { return }
    // Use service
}

Now that I have a service, I query the service for characteristics with discoverCharacteristics2. Here I pick one and ask for all characteristics for that service.

service = services[idx]
peripheral.discoverCharacteristics(nil, for: service)

As before, passing nil requests everything: in this case all characteristics. I implement aother delegate function from CBPeripheralDelegate to listen for characteristics:

func peripheral(_ peripheral: CBPeripheral,
        didDiscoverCharacteristicsFor service: CBService, error: Error?) {
    guard let characteristics = peripheral.characterists else { return }
}

Almost done! I have an array of characteristics, so I can finally ask for the value of one of these characteristics. I'll just pick one out of the array and try to read it:

let characteristic = characteristics[idx]
peripheral.readValue(for: characteristic)

Another delegate callback on my CBPeripheralDelgate receives the actual data.

func peripheral(_ peripheral: CBPeripheral,
        didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        guard let data = characteristic.value else { return }
        // Woohoo! Data!
}

Yay! I finally have my data, in the form of a Data object. Now it's up to me to interpret that data. Is it a Float? Int? Signed or unsiged? Maybe it's a String. I wonder how it's encoded...

To recap, here are the steps for all this back and forth.

  1. Create a CBCentralManager.
  2. Make sure the Bluetooth radio is on, then discover peripherals.
  3. Get an instance of a CBPeripheral from a CBCentralManagerDelegate callback.
  4. Tell the central manager to connect to the peripheral.
  5. Get notified of the connection in a CBCentralManagerDelegate callback.
  6. Query the peripheral about included services.
  7. Get notified of discovered peripherals in a CBPeripheralDelegate callback.
  8. Query the peripheral about charactieristics in a given service.
  9. Get notified of discovered characteristics in a CBPeripheralDelegate callback.
  10. Query the peripheral about the value of a give characteristic.
  11. Get notified of the value in a CBPeripheralDelgate callback.
  12. Interperet data.

  1. This little handoff can require thought. I find that I'm switching view controllers as I switch from the CBCentralManager to the CBPeripheral, and the two controllers need to touch both the CBCentralManager and the CBPeripheral. Take some time to plan out the proper ownership. I'll ignore all such complications for now. 

  2. You can also query a service for other services with discoverIncludedServices(_:for:). I won't be doing that here. 

← Previous Page 2 of 2