Chris' Blog.

My occasional thoughts on iOS development, developers careers, trying to make an income from the App Store, and updates on life in general.

Swift Common Crypto

I tried recently to find a pure-Swift CommonCrypto wrapper, but all I could find were Obj-C wrapper libraries that were overkill. So, here I'd like to demonstrate how to bridge from Swift (version 5) to all those awkward UnsafeRawPointer and UnsafeMutableRawPointer types that face you when using libraries like CommonCrypto.

I'll also share some best-practices when using encryption, to wit:

  • Use CBC mode, not ECB.
  • Use PKCS7 for padding so that your input size doesn't have to be a multiple of 128 bits.
  • Generate a fresh Initialisation Vector each encryption. It doesn't need to be hidden, just random.

CommonCrypto wrapper

Note that operation/algorithm/options are ints, because that's the type of the constants (kCCEncrypt, kCCDecrypt, kCCOptionPKCS7Padding, etc), whereas the param types are Int32. This is intended to make life more convenient for the caller.

CCrypt wants inputs (eg: key, iv, dataIn) like so: iv: UnsafeRawPointer!. To bridge from Swift to C, these steps are use for input data:

  • Convert Data to UnsafeRawBufferPointer by calling withUnsafeBytes
  • Then convert UnsafeRawBufferPointer to UnsafeRawPointer by calling baseAddress

Output data is a param of type UnsafeMutableRawPointer. To handle these, first allocate a buffer with: UnsafeMutableRawPointer.allocate(byteCount:alignment:). You're responsible to deallocate that, so defer it immediately: defer { dataOut.deallocate() }. Finally, copy its contents back to a Swift Data with Data(bytes:count:).

func crypt(operation: Int, algorithm: Int, options: Int, key: Data,
        initializationVector: Data, dataIn: Data) -> Data? {
    return key.withUnsafeBytes { keyUnsafeRawBufferPointer in
        return dataIn.withUnsafeBytes { dataInUnsafeRawBufferPointer in
            return initializationVector.withUnsafeBytes { ivUnsafeRawBufferPointer in
                // Give the data out some breathing room for PKCS7's padding.
                let dataOutSize: Int = dataIn.count + kCCBlockSizeAES128*2
                let dataOut = UnsafeMutableRawPointer.allocate(byteCount: dataOutSize,
                    alignment: 1)
                defer { dataOut.deallocate() }
                var dataOutMoved: Int = 0
                let status = CCCrypt(CCOperation(operation), CCAlgorithm(algorithm),
                    keyUnsafeRawBufferPointer.baseAddress, key.count,
                    dataInUnsafeRawBufferPointer.baseAddress, dataIn.count,
                    dataOut, dataOutSize, &dataOutMoved)
                guard status == kCCSuccess else { return nil }
                return Data(bytes: dataOut, count: dataOutMoved)

Random bytes generation

Here's my pure-Swift wrapper for generating random bytes:

func randomGenerateBytes(count: Int) -> Data? {
    let bytes = UnsafeMutableRawPointer.allocate(byteCount: count, alignment: 1)
    defer { bytes.deallocate() }
    let status = CCRandomGenerateBytes(bytes, count)
    guard status == kCCSuccess else { return nil }
    return Data(bytes: bytes, count: count)

Simple encryption and decryption

Here's my using-the-good-options version of encryption/decryption:

extension Data {
    /// Encrypts for you with all the good options turned on: CBC, an IV, PKCS7
    /// padding (so your input data doesn't have to be any particular length).
    /// Key can be 128, 192, or 256 bits.
    /// Generates a fresh IV for you each time, and prefixes it to the
    /// returned ciphertext.
    func encryptAES256_CBC_PKCS7_IV(key: Data) -> Data? {
        guard let iv = randomGenerateBytes(count: kCCBlockSizeAES128) else { return nil }
        // No option is needed for CBC, it is on by default.
        guard let ciphertext = crypt(operation: kCCEncrypt,
                                    algorithm: kCCAlgorithmAES,
                                    options: kCCOptionPKCS7Padding,
                                    key: key,
                                    initializationVector: iv,
                                    dataIn: self) else { return nil }
        return iv + ciphertext

    /// Decrypts self, where self is the IV then the ciphertext.
    /// Key can be 128/192/256 bits.
    func decryptAES256_CBC_PKCS7_IV(key: Data) -> Data? {
        guard count > kCCBlockSizeAES128 else { return nil }
        let iv = prefix(kCCBlockSizeAES128)
        let ciphertext = suffix(from: kCCBlockSizeAES128)
        return crypt(operation: kCCDecrypt, algorithm: kCCAlgorithmAES,
            options: kCCOptionPKCS7Padding, key: key, initializationVector: iv,
            dataIn: ciphertext)


And here's how you'd use it, although you shouldn't be force-unwrapping things in practice!

let key = randomGenerateBytes(count: 256/8)!
let superDuperSecret = "The quick brown fox jumps over the lazy dog".data(using: .utf8)!
let encrypted = superDuperSecret.encryptAES256_CBC_PKCS7_IV(key: key)!
let decrypted = encrypted.decryptAES256_CBC_PKCS7_IV(key: key)!
print(String(data: decrypted, encoding: .utf8)!)


Once you're happy to only support iOS13+, you should consider using CryptoKit over CommonCrypto:

Having said that, I hope this post is still a good example of how to handle C libraries that require UnsafeRawPointers.

MIT license applies. No warranties. You should get a security team to review all code in your project, such as this.

Thanks for reading, and have a great week!

Photo by Gabriel Wasylko on Unsplash


Here is some sample code for pairing your iOS app with a Bluetooth LE peripheral. I'd like to think of it as a good starting point for your particular use-case, where you'd likely make a lot of changes. It handles the usual requirements like service discovery, reconnecting automatically when you come in and out of range, and reconnecting on app startup.

Before you simply copy and paste and use it, please read my earlier Bluetooth article for background and other necessary information.

How to use this

  • Customise the service id, characteristic id, and the desired manufacturer data.
  • In your AppDelegate, prod the singleton to life with _ = MyBluetoothManager.shared
  • Call MyBluetoothManager.shared.scan() when you'd like to scan for the peripheral.
  • Implement some sort of notification system for whenever the state changes, eg in a didChange handler, and listen to this from other parts of your app to eg progress the UI to the 'paired' screen.
  • Call MyBluetoothManager.shared.disconnect(forget: true) when the user wants to unpair.
  • Call MyBluetoothManager.shared.write(data: foo) when you'd like to send data to the peripheral.
  • Customise didUpdateValueFor characteristic to handle data coming from the peripheral.
  • And of course, being Bluetooth, cross your fingers and hope for the best!

MIT license applies.

import CoreBluetooth

private let restoreIdKey = "MyBluetoothManager"
private let peripheralIdDefaultsKey = "MyBluetoothManagerPeripheralId"
private let myDesiredServiceId = CBUUID(string:
private let myDesiredCharacteristicId = CBUUID(string:
private let desiredManufacturerData = Data(base64Encoded: "foobar==")!
private let outOfRangeHeuristics: Set<CBError.Code> = [.unknown,
    .connectionTimeout, .peripheralDisconnected, .connectionFailed]

/// This manages a bluetooth peripheral. This is intended as a starting point
/// for you to customise from.
/// Read for a
/// background in how to set this all up.
class MyBluetoothManager {
    static let shared = MyBluetoothManager()

    let central = CBCentralManager(delegate: MyCentralManagerDelegate.shared,
        queue: nil, options: [
        CBCentralManagerOptionRestoreIdentifierKey: restoreIdKey,

    /// The 'state machine' for remembering where we're up to.
    var state = State.poweredOff
    enum State {
        case poweredOff
        case restoringConnectingPeripheral(CBPeripheral)
        case restoringConnectedPeripheral(CBPeripheral)
        case disconnected
        case scanning(Countdown)
        case connecting(CBPeripheral, Countdown)
        case discoveringServices(CBPeripheral, Countdown)
        case discoveringCharacteristics(CBPeripheral, Countdown)
        case connected(CBPeripheral)
        case outOfRange(CBPeripheral)

        var peripheral: CBPeripheral? {
            switch self {
            case .poweredOff: return nil
            case .restoringConnectingPeripheral(let p): return p
            case .restoringConnectedPeripheral(let p): return p
            case .disconnected: return nil
            case .scanning: return nil
            case .connecting(let p, _): return p
            case .discoveringServices(let p, _): return p
            case .discoveringCharacteristics(let p, _): return p
            case .connected(let p): return p
            case .outOfRange(let p): return p

    // Begin scanning here!
    func scan() {
        guard central.state == .poweredOn else {
            print("Cannot scan, BT is not powered on")

        // Scan!
        central.scanForPeripherals(withServices: [myDesiredServiceId], options: nil)
        state = .scanning(Countdown(seconds: 10, closure: {
            self.state = .disconnected
            print("Scan timed out")

    /// Call this with forget: true to do a proper unpairing such that it won't
    /// try reconnect next startup.
    func disconnect(forget: Bool = false) {
        if let peripheral = state.peripheral {
        if forget {
            UserDefaults.standard.removeObject(forKey: peripheralIdDefaultsKey)
        state = .disconnected

    func connect(peripheral: CBPeripheral) {
        // Connect!
        // Note: We're retaining the peripheral in the state enum because Apple
        // says: "Pending attempts are cancelled automatically upon
        // deallocation of peripheral"
        central.connect(peripheral, options: nil)
        state = .connecting(peripheral, Countdown(seconds: 10, closure: {
            self.state = .disconnected
            print("Connect timed out")

    func discoverServices(peripheral: CBPeripheral) {
        peripheral.delegate = MyPeripheralDelegate.shared
        state = .discoveringServices(peripheral, Countdown(seconds: 10, closure: {
            print("Could not discover services")

    func discoverCharacteristics(peripheral: CBPeripheral) {
        guard let myDesiredService = peripheral.myDesiredService else {
        peripheral.delegate = MyPeripheralDelegate.shared
            for: myDesiredService)
        state = .discoveringCharacteristics(peripheral, Countdown(seconds: 10,
            closure: {
            print("Could not discover characteristics")

    func setConnected(peripheral: CBPeripheral) {
        guard let myDesiredCharacteristic = peripheral.myDesiredCharacteristic
            else {
            print("Missing characteristic")

        // Remember the ID for startup reconnecting.
            forKey: peripheralIdDefaultsKey)

        // Ask for notifications when the peripheral sends us data.
        // TODO another state waiting for this?
        peripheral.delegate = MyPeripheralDelegate.shared
        peripheral.setNotifyValue(true, for: myDesiredCharacteristic)

        state = .connected(peripheral)

    /// Write data to the peripheral.
    func write(data: Data) throws {
        guard case .connected(let peripheral) = state else {
            throw Errors.notConnected
        guard let characteristic = peripheral.myDesiredCharacteristic else {
            throw Errors.missingCharacteristic
        peripheral.writeValue(data, for: characteristic, type: .withoutResponse)
        // .withResponse is more expensive but gives you confirmation.
        // It's an exercise for the reader to ask for a response and handle
        // timeouts waiting for said response.
        // I found it simpler to deal with that at a higher level in a
        // messaging framework.

    enum Errors: Error {
        case notConnected
        case missingCharacteristic


extension CBPeripheral {
    /// Helper to find the service we're interested in.
    var myDesiredService: CBService? {
        guard let services = services else { return nil }
        return services.first { $0.uuid == myDesiredServiceId }

    /// Helper to find the characteristic we're interested in.
    var myDesiredCharacteristic: CBCharacteristic? {
        guard let characteristics = myDesiredService?.characteristics else {
            return nil
        return characteristics.first { $0.uuid == myDesiredCharacteristicId }

class MyPeripheralDelegate: NSObject, CBPeripheralDelegate {
    static let shared = MyPeripheralDelegate()

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        // Ignore services discovered late.
        guard case .discoveringServices = MyBluetoothManager.shared.state else {

        if let error = error {
            print("Failed to discover services: \(error)")
        guard peripheral.myDesiredService != nil else {
            print("Desired service missing")

        // Progress to the next step.
        MyBluetoothManager.shared.discoverCharacteristics(peripheral: peripheral)

    func peripheral(_ peripheral: CBPeripheral,
            didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        // Ignore characteristics arriving late.
        guard case .discoveringCharacteristics =
            MyBluetoothManager.shared.state else { return }

        if let error = error {
            print("Failed to discover characteristics: \(error)")
        guard peripheral.myDesiredCharacteristic != nil else {
            print("Desired characteristic missing")

        // Ready to go!
        MyBluetoothManager.shared.setConnected(peripheral: peripheral)

    func peripheral(_ peripheral: CBPeripheral,
            didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {

        // This is where the peripheral sends you data!
        // Exercise for the reader: handle the characteristic.value, eg buffer
        // and scan for JSON between STX and ETX markers.

    /// Called when .withResponse is used.
    func peripheral(_ peripheral: CBPeripheral,
            didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if let error = error {
            print("Error writing to characteristic: \(error)")

    func peripheral(_ peripheral: CBPeripheral,
            didUpdateNotificationStateFor characteristic: CBCharacteristic,
            error: Error?) {
        // TODO cancel a setNotifyValue timeout if no error.

class MyCentralManagerDelegate: NSObject, CBCentralManagerDelegate {
    static let shared = MyCentralManagerDelegate()

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state == .poweredOn {
            // Are we transitioning from BT off to BT ready?
            if case .poweredOff = MyBluetoothManager.shared.state {
                // Firstly, try to reconnect:
                if let peripheralIdStr = UserDefaults.standard
                        .object(forKey: peripheralIdDefaultsKey) as? String,
                    let peripheralId = UUID(uuidString: peripheralIdStr),
                    let previouslyConnected = central
                        .retrievePeripherals(withIdentifiers: [peripheralId])
                        .first {
                        peripheral: previouslyConnected)

                    // Next, try for ones that are connected to the system:
                } else if let systemConnected = central
                        [myDesiredServiceId]).first {
                    MyBluetoothManager.shared.connect(peripheral: systemConnected)

                } else {
                    // Not an error, simply the case that they've never paired
                    // before, or they did a manual unpair:
                    MyBluetoothManager.shared.state = .disconnected

            // Did CoreBluetooth wake us up with a peripheral that was connecting?
            if case .restoringConnectingPeripheral(let peripheral) =
                    MyBluetoothManager.shared.state {
                MyBluetoothManager.shared.connect(peripheral: peripheral)

            // CoreBluetooth woke us with a 'connected' peripheral, but we had
            // to wait until 'poweredOn' state:
            if case .restoringConnectedPeripheral(let peripheral) =
                    MyBluetoothManager.shared.state {
                if peripheral.myDesiredCharacteristic == nil {
                        peripheral: peripheral)
                } else {
                    MyBluetoothManager.shared.setConnected(peripheral: peripheral)
        } else { // Turned off.
            MyBluetoothManager.shared.state = .poweredOff

    // Apple says: This is the first method invoked when your app is relaunched
    // into the background to complete some Bluetooth-related task.
    func centralManager(_ central: CBCentralManager,
            willRestoreState dict: [String : Any]) {
        let peripherals: [CBPeripheral] = dict[
            CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral] ?? []
        if peripherals.count > 1 {
            print("Warning: willRestoreState called with >1 connection")
        // We have a peripheral supplied, but we can't touch it until
        // `central.state == .poweredOn`, so we store it in the state
        // machine enum for later use.
        if let peripheral = peripherals.first {
            switch peripheral.state {
            case .connecting: // I've only seen this happen when
                // re-launching attached to Xcode.
                MyBluetoothManager.shared.state =

            case .connected: // Store for connection / requesting
                // notifications when BT starts.
                MyBluetoothManager.shared.state =
            default: break

    func centralManager(_ central: CBCentralManager,
            didDiscover peripheral: CBPeripheral,
            advertisementData: [String : Any], rssi RSSI: NSNumber) {
        guard case .scanning = MyBluetoothManager.shared.state else { return }

        // You might want to skip this manufacturer data check.
        guard let mfgData =
                advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data,
            mfgData == desiredManufacturerData else {
            print("Missing/wrong manufacturer data")

        MyBluetoothManager.shared.connect(peripheral: peripheral)

    func centralManager(_ central: CBCentralManager,
            didConnect peripheral: CBPeripheral) {
        if peripheral.myDesiredCharacteristic == nil {
            MyBluetoothManager.shared.discoverServices(peripheral: peripheral)
        } else {
            MyBluetoothManager.shared.setConnected(peripheral: peripheral)

    func centralManager(_ central: CBCentralManager,
            didFailToConnect peripheral: CBPeripheral, error: Error?) {
        MyBluetoothManager.shared.state = .disconnected

    func centralManager(_ central: CBCentralManager,
            didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        // Did our currently-connected peripheral just disconnect?
        if MyBluetoothManager.shared.state.peripheral?.identifier ==
                peripheral.identifier {
            // IME the error codes encountered are:
            // 0 = rebooting the peripheral.
            // 6 = out of range.
            if let error = error,
                (error as NSError).domain == CBErrorDomain,
                let code = CBError.Code(rawValue: (error as NSError).code),
                outOfRangeHeuristics.contains(code) {
                // Try reconnect without setting a timeout in the state machine.
                // With CB, it's like saying 'please reconnect me at any point
                // in the future if this peripheral comes back into range'.
                MyBluetoothManager.shared.central.connect(peripheral, options: nil)
                MyBluetoothManager.shared.state = .outOfRange(peripheral)
            } else {
                // Likely a deliberate unpairing.
                MyBluetoothManager.shared.state = .disconnected

/// Timer wrapper that automatically invalidates when released.
/// Read more:
class Countdown {
    let timer: Timer

    init(seconds: TimeInterval, closure: @escaping () -> ()) {
        timer = Timer.scheduledTimer(withTimeInterval: seconds,
                repeats: false, block: { _ in

    deinit {

Thanks for reading, and have a great week!

Photo by Katherine Chase on Unsplash


In this post, I'd like to explain how to get your app to pair with a Bluetooth LE peripheral, reconnect on subsequent app launches, and stay connected as the devices comes in and out of range - handle all the realistic scenarios. I'll also talk about the gotchas when working with CoreBluetooth, and outline a good example that you can build on.

Sample code to demonstrate all this is here in a subsequent article, please check it out.

Important things to know

  • Bluetooth Low Energy is what you want to be using - not old-style Bluetooth. If you want the latter, this article won't be much help.
  • CoreBluetooth is the iOS framework for dealing with bluetooth. It's probably worth a read: Core Bluetooth Guide and Core Bluetooth API.
  • BLE devices have 'services', and those services have 'characteristics'.
  • A 'profile' is an informal grouping of 'services', and doesn't get a mention in Core Bluetooth.
  • BLE is all about reading and writing values (raw byte arrays) to characteristics.
  • If you want to send/receive messages (eg JSON), commonly people build something custom on top of a pair of characteristics.
  • You can subscribe to changes to a characteristic, allowing a peripheral to 'push' data to you when it wants.


Before delving into the nitty gritty, I'll explain what needs to happen at a high level.

Here's how pairing with a new device works:

  1. Wait for CoreBluetooth to get to the poweredOn state.
  2. Scan for peripherals (that have the services you're interested in).
  3. Connect to a peripheral.
  4. Discover its services.
  5. Discover the services' characteristics.
  6. Subscribe to any characteristics where you want the peripheral to be able to 'push' data to you.
  7. Done!

And here's how reconnecting to that device on subsequent launches of your app works:

  1. Wait for CoreBluetooth to get to the 'poweredOn' state.
  2. Retrieve the previously-connected peripheral
  3. Connect, discover, etc.

And here's how you re-connect when the device goes out of range:

  1. You're connected normally...
  2. didDisconnect is called with error .connectionTimeout
  3. Call 'connect' and never timeout
  4. didConnect is called later on when the device comes into range.

Project Setup

Firstly, you'll need to include CoreBluetooth into your app. In Xcode, go into your target settings > General > Linked Frameworks and Libraries, click '+' and select CoreBluetooth.

Next, you should consider enabling background mode if your use-case requires it. If so, head for target settings > Capabilities > Background Modes > Uses Bluetooth LE accessories.

Go to your Info.plist, and add a key named 'Privacy - Bluetooth Peripheral Usage Description' and set the value to something like 'MyAwesomeApp connects to your MyBrilliantPeripheral via Bluetooth'. This is shown to the user by iOS.

App Startup

At app startup, create a CBCentralManager instance. Typically you'll do this in some kind of singleton. Since it requires the delegate passed to the initialiser, you can't use the same singleton as the delegate due to Swift's init rules. You must also pass in a restore ID for reconnects across app launches to work. Hopefully you're only pairing to one device, and thus can use a constant for this id. If you pass 'nil' for the queue, all the central/peripheral delegates will be called on the main thread, which is probably reasonable.

class MyBluetoothLEManager {
    static let shared = MyBluetoothLEManager()

    private let central = CBCentralManager(
        delegate: MyCBCentralManagerDelegate.shared, 
        queue: nil, 
        options: [
            // Alert the user if BT is turned off.
            CBCentralManagerOptionShowPowerAlertKey: true,

            // ID to allow restoration.
            CBCentralManagerOptionRestoreIdentifierKey: "MyRestoreIdentifierKey",

This 'central' will initially be unusable, you must wait for it to call your delegate's centralManagerDidUpdateState with central.state == poweredOn before you can do anything. This can be tricky, which is why I recommend using a State Machine for dealing with Core Bluetooth. I've written in the past about State Machines before, I recommend reading about it to get a background. In the case here, I'm not going the whole hog with an Event handler, I'm just using an enum with associated values, which I think is a good balance.

Before the poweredOn state, however, Core Bluetooth may call willRestoreState and give you one or more CBPeripherals. This occurs when your app is relaunched into the background to handle some Bluetooth task, eg a subscribed characteristic value has changed. The given peripheral's state should be connected, however I've seen it as connecting only when running with Xcode's debugger attached. The trick is to store that peripheral somewhere, then wait for the poweredOn state, and then use it. I'll show you later how to do this neatly with a state machine.

Once you're reached the powered on state, there is a multitude of options ahead:

  • If willRestoreState was called before, and the peripheral is in connecting state, call connect.
  • If willRestoreState was called before, and the peripheral is in connected state, and its services and their characteristics are filled in, you're ready to use it!
  • If willRestoreState was called before, and the peripheral is in connected state, but services and/or characteristics are not filled in, call discoverServices then discoverCharacteristics.
  • Try central.retrievePeripherals(withIdentifiers: to find a previously-paired peripheral, and then connect to it.
  • Try central.retrieveConnectedPeripherals(withServices: to find your previously-paired peripheral that is connected to iOS but not your app, and connect to it.
  • Failing all that, you're likely in a situation where your app has simply not been paired before.


Once you are in poweredOn state but aren't connected, and your user has selected to initiate pairing, you need to call central.scanForPeripherals(withServices: [CBUUID(string: "SOME-UUID")], options: nil).

The UUID is used as a battery-saving measure for you to tell iOS to filter the peripherals to only the ones with the service(s) you're interested in. Your hardware team will be able to give you this ID, otherwise you can use Apple Hardware IO Tools > Bluetooth Explorer to find it.

Core Bluetooth will never timeout when scanning, so you'll probably want to create some timeout of your own (10s is a good starting point), and call central.stopScan() at that point. Again, the State Machine is a great way to handle these timeouts neatly, which I will explain further below.

When it finds something, it will call the didDiscover:advertisementData:rssi: delegate with the discovered peripheral. Your hardware team may want to put some custom data in the advertising packets, which will be given to you here as advertisementData[CBAdvertisementDataManufacturerDataKey] as? Data.


If this is the peripheral you want, you must call central.stopScan(), then myCentral.connect(peripheral, options: []), and retain the CBPeripheral somewhere otherwise Core Bluetooth will drop it before the connection completes.

connect will not timeout (which is handy for out-of-range reconnections), so you must implement your own timeout. I like to embed a timeout in the State Machine's as an enum associated value. This way the timeout gets automatically cancelled when the state progresses. To do this, I use Countdown from an earlier post I wrote called 'Timers without circular references'.

Creating the timeout looks like this:

let timeout = Countdown(seconds: 10, closure: {
    peripheral.cancelPeripheralConnection(central: self.myCentral)
    self.state = .disconnected

And embedding it in the state machine looks like this:

state = .connecting(peripheral, timeout)

In this way the state enum retains the peripheral for us too, which is essential to keep Core Bluetooth happy.

After a call to connect, your delegate will be called with either didConnect: or didFailToConnect:error:.

Upon didConnect, you should save the peripheral.identifier UUID somewhere (eg UserDefaults) so it can be reused at next app launch to reconnect.


Once paired, one thing you'll need to deal with is the peripheral coming in and out of range, and reconnecting when that happens.

Your CBCentralManagerDelegate will be told via didDisconnectPeripheral:error: that something's gone wrong. At this point some heuristics is involved to figure out of this is an out-of-range issue, as opposed to the peripheral deliberately unpairing from you at the user's selection. Here's what has worked for me:

  • If the error is nil, it's a disconnection initiated by your own code, so simply go to 'disconnected' state.
  • Check if the error is a CB error with: if (error as NSError).domain == CBErrorDomain
  • Cast the error code to a CB enum like so: if let code = CBError.Code(rawValue: (error as NSError).code)
  • Make a set of errors that are probably out-of-range, here's what I found worked: let outOfRangeHeuristics: Set<CBError.Code> = [.unknown, .connectionTimeout, .peripheralDisconnected, .connectionFailed]
  • Check if the error is one of the above: if outOfRangeHeuristics.contains(code), and if so, try to reconnect (explained below).
  • After all those checks, at this point it's up to you if you want to try reconnect or not. I don't try, personally.

If you've decided it's probably an out-of-range and it's worth trying to reconnect, the trick is to simply call central.connect(peripheral, options:[]) and never set your own timeout. What you're doing here is effectively telling iOS 'I'm interested in connecting, let me know if you ever see this peripheral again'. This works because connect never times out.


Once paired, you'll need to 'discover' the Services and Characteristics of your BT peripheral. Before you do this though, check the peripheral's services for the service(s) whose uuid matches the UUID you're interested in. If it's already there (cached by iOS), you can skip service discovery.

To perform service discovery, set the delegate of your CBPeripheral as you deem appropriate (eg a singleton). Then call discoverServices([CBUUID(string: "UUID-OF-INTERESTING-SERVICE"), ...]), passing in an array of the UUIDs of the services you are interested in for a speed-up.

You won't get a callback if this fails, so again you'll have to set some kind of timeout. You'll receive a didDiscoverServices: call to your delegate once the service has been discovered. Once you've found the service you want, progress to the characteristic discovery step.

Characteristic discovery is much the same as for services: First check the CBService.characteristics, looking for a myCharacteristic.uuid match to see if it's already cached. If not, call myPeripheral.discoverCharacteristics([CBUUID(string: "UUID-OF-INTERESTING-CHARACTERISTIC"), ...], for: myService), and wait for the didDiscoverCharacteristicsForService:error: delegate callback.


After completing the characteristic discovery step, you're ready to 'listen' to your peripheral. This is done by subscribing to changes on one (or more) of the characteristic values, by calling myPeripheral.setNotifyValue(true, for: myCharacteristic).

Your delegate will be called back on didUpdateNotificationStateFor:error: when this subscription has been successful or not.

Whenever the peripheral wants to update that value and send you some new data, your delegate will be called on didUpdateValueFor:error:, and inside that handler you can check myCharacteristic.value to see the value.


Once you're paired, and the services/characteristics have all been discovered, you're ready to 'talk' to your BTLE peripheral. The way this works is by setting the 'value' of characteristics. These values are raw bytes, Data in Swift. Locate the characteristic you're interested in, and call myPeripheral.writeValue(someData, for: myCharacteristic, type: .withResponse (or withoutResponse)).

If you don't need a response, type: .withoutResponse will presumably use less bluetooth packets / battery life.

Otherwise, type: .withResponse will result in a didWriteValueForCharacteristic:error: call to your delegate to let you know how that write went.

Bluetooth is a bit vague on how much big a chunk of data can be set on a bluetooth characteristic. iOS appears to negotiate a size, and if you set a data bigger than that size, it chunks the data up and sets the characteristic value one chunk at a time. This is fantastic news if you're using characteristics as a stream for some sort of JSON messaging.


Since characteristic values are so simple, it's common to lay some sort of JSON messaging on top of a pair of characteristics (one for reading, one for writing). Typically you can send some start-of-message byte (STX aka 'start of text' aka '\x02' is as good as any), then your JSON, then an end-of-message byte (eg ETX aka 'end of text' aka \x03).

At the respective (iOS and peripheral) ends, all received values are iterated byte-by-byte thus:

if byte == 2 (STX), empty the buffer.
else if byte == 3 (ETX), try to JSON-parse the buffer and send the message to your message handler.
else append byte to the buffer.

Such an arrangement works very well with the way Core Bluetooth 'chunks' large data written to a characteristic value.


This is easy! When the user requests that you unpair, simply call: central.cancelPeripheralConnection(peripheral), then dereference the peripheral, then erase the peripheral identifier you stored in your UserDefaults.

Gotchas / Notes

  • You can't use the central manager immediately - it must change its state to poweredOn
  • Calls to connect never timeout. This is very useful for when a device goes out of range: Simply call connect, and you'll get notified when it comes back in range, however long that may take.
  • When connecting to a peripheral, you must retain the CBPeripheral for the duration or iOS will cancel the connection.
  • It's hard to know the maximum size of a characteristic's value. As best I can tell, iOS negotiates it with the peripheral behind the scenes based on which BT version is being used, and tries to push it higher than the lowest-common-denominator that the standard officially supports. In any case, you don't really need to know the size when setting a value: just set it to as big a Data as you like (within reason), and Core Bluetooth will automatically 'chunk' it for you and set the characteristic's value to those chunks, one Bluetooth packet at a time. This is very useful for the common case where you're using characteristics for sending JSON messages.
  • Typically you'll have at least two characteristics: One for reading, one for writing.

Thanks for reading, and have a great week!

Photo by Joel Filipe on Unsplash

You can see older posts in the right panel, under 'archive'.


Pure Swift 5 CommonCrypto AES Encryption 9 Jun 2019

Bluetooth example code for Swift/iOS 6 Jun 2019

Talking to a Bluetooth LE peripheral with Swift/iOS 18 May 2019

Obfuscating Keys using Swift 5 May 2019

State Machines in Swift using enums 10 Apr 2019

iOS timers without circular references with Pendulum 28 Mar 2019

Pragmatic Reactive Programming 11 Oct 2017

React Native first impressions 7 Apr 2017

Gondola 26 Feb 2017

Scalable Swift 22 Nov 2016

Swift 3 Migration 6 Nov 2016

Enum-Driven View Controllers 3 Jan 2016

Status bar colours: Everything there is to know 30 Dec 2015

Android server 20 Dec 2015

Generating heightmap terrain with Swift 8 Nov 2015

Swift Education Screencasts 27 Oct 2015

Swift Image Cache 24 Sep 2015

Don't be slack 13 Sep 2015

Swift KVO alternative 23 Jul 2015

Swift Keychain wrapper 21 Jun 2015

Swift NSURLSession wrapper 12 Jun 2015

iOS8 View Controller transitioning bug 17 Apr 2015

IB Designable 18 Mar 2015

iOS App Architecture 2 Mar 2015

Video Course Launch 14 Feb 2015

Video Course Pre-launch 8 Feb 2015

Blogging Platforms 13 Jan 2015

Mobile in 2014 - Year in Review 11 Jan 2015

Secret Keys talk 16 Nov 2014

Dimmi 11 Nov 2014

Project setup in Xcode6 22 Oct 2014

Uploading to an S3 bucket from iOS 15 Oct 2014

iOS8 App Testing Roundup 28 Sep 2014

Storing obfuscated secret keys in your iOS app 16 Sep 2014

Getting Core Location / CLLocationManager to work on iOS8 14 Sep 2014

Accessing the response body in failure blocks with AFNetworking 2 10 Sep 2014

How to allow your UITextFields to scroll out of the way of the keyboard 8 Sep 2014

How to subclass UIButton in iOS7 and make a UIButtonTypeSystem 4 Sep 2014

New season 1 Aug 2014

House finished 17 Jun 2014

WebP decoding on iOS 9 Feb 2014

Moving on again 22 Jan 2014

Lossy images for retina iPads - JPEG vs WebP 30 Nov 2013

Career options I wish I knew about when I was younger 20 Oct 2013

Positivity and your friends 7 Oct 2013

Tactility 26 Jul 2013

WWDC-induced narcolepsy 15 Jul 2013

Back on rails 31 May 2013

Full circle 6 May 2013

Programmatic UI on iOS 3 May 2013

Screencasts and positivity 8 Apr 2013

Year of positivity 14 Mar 2013

iOS Dev State of the Union 6 Feb 2013

Adventures with IAPs 3 Feb 2013

No longer a Googler 23 Dec 2012

Localising iPhone apps with Microsoft Translator 8 Dec 2012

Fight back (app biz update 13) 12 Nov 2012

Sent to the backburner (app biz update 12) 25 Oct 2012

Lisi Schappi 7 Oct 2012

Today's happy plateau (app biz update 11) 26 Aug 2012

First week's sales of Today (app biz update 10) 19 Aug 2012

Today launch! And a difficult decision made... (app biz update 9) 15 Aug 2012

Approved! (app biz update 8) 5 Aug 2012

Creating a graph in Objective-C on the iPhone 3 Aug 2012

Hurry up and wait (app biz update 7) 30 Jul 2012

Today app marketing site 27 Jul 2012

Today app submitted 25 Jul 2012

UIAlertView input wrapper 24 Jul 2012

Mentoring 23 Jul 2012

This is too hard! (app biz update 6) 20 Jul 2012

Perspectives (app biz update 5) 9 Jul 2012

4th starting-my-own-biz update 1 Jul 2012

ScrumFox landing page 28 Jun 2012

Server Scope landing page 27 Jun 2012

Telstra Calls and Data Usage 26 Jun 2012

Service History + Dropbox 26 Jun 2012

Impromptu Presenter 26 Jun 2012

Fertility Tracker 26 Jun 2012

Baby Allergy Tracker 26 Jun 2012

Starting my own business, update 3 22 Jun 2012

Starting my own business, update 2 17 Jun 2012

Starting my own business - First update 10 Jun 2012

I must be crazy 6 Jun 2012

Finding your location on an iPhone 7 May 2012

A generous career 4 May 2012

Skeleton Key Cocoaheads presentation 3 May 2012

CHBgDropboxSync - Dropbox auto-sync for your iOS apps 1 May 2012

That book about that Steve Jobs guy 30 Apr 2012

Another app marketing idea 23 Apr 2012

Sweet grouped tables on the iPhone 17 Apr 2012

Skeleton Key App 11 Apr 2012

Another app marketing idea... 5 Apr 2012

Quickly check for any missing retina graphics in your project 3 Apr 2012

Skeleton Key Password Manager with Dropbox 2 Apr 2012

RC Boat motor finally mounted 2 Apr 2012

Promoting apps presentation slides 1 Apr 2012

How i just wasted a month on my latest app, and how you don't need to 26 Mar 2012

The Finishing Line 20 Mar 2012

Using Launchd to run a script every 5 mins on a Mac 20 Feb 2012

Generating AES256 keys from a password/passphrase in ObjC 20 Feb 2012

Indie iPhone app marketing, part 2 19 Feb 2012

My App Manifesto: Syncing + Dropbox + YAML = Awesome 15 Feb 2012

Indie iPhone App Marketing part 1 7 Feb 2012

Perspectives 2 Feb 2012

Accountability and Free Will 1 Feb 2012

Badassery 31 Jan 2012

Sacrifice 30 Jan 2012

Lead Yourself First 29 Jan 2012

How to ping a server in Objective-C / iPhone 26 Jan 2012

iOS Automated Builds with Xcode4 16 Jan 2012

Xcode 4 - Command line builds of iPhone apps 15 Jan 2012

Guest post by Jason McDougall 13 Jan 2012

Scouts, Games and Motivation 10 Jan 2012

2011 Re-cap 8 Jan 2012

Ruby script to increment a build number 4 Jan 2012

Turning 30? All ideas, no execution? 18 Dec 2011

CHDropboxSync - simply sync your iOS app's documents to Dropbox 14 Dec 2011

Deep-enumerating a directory on the iphone, getting file attributes as you go 10 Dec 2011

Getting a date without the time component in objective-c 6 Dec 2011

Memory management in Objective-C 4 Dec 2011

Starting small 29 Nov 2011

Dictionary Types Helper 29 Nov 2011

Observer Pattern in Objective-C 16 Nov 2011

Why you should give presentations 13 Nov 2011

How to get a programming or design job in Sydney 9 Nov 2011

Custom nav bar / toolbar backgrounds in iOS5 8 Nov 2011

Stuck 27 Oct 2011

Dead easy singletons in Obj-C 19 Oct 2011

JSON vs OCON (Objective-C Object Notation) 18 Oct 2011

In defence of Objective-C 16 Oct 2011

Update the MessagePack objective-c library to support packing 12 Oct 2011

Icons 11 Oct 2011

How to host a site on Amazon AWS S3, step-by-step 7 Oct 2011

Drawing a textured pattern over the default UINavigationBar 6 Oct 2011

Markdown Presentations 1 Oct 2011

More MegaComet testing: Ruling out keepalives 15 Sep 2011

MegaComet test #4 - This time with more kernel 14 Sep 2011

Building People 10 Sep 2011

Half way there: Getting MegaComet to 523,000 concurrent HTTP connections 5 Sep 2011

Making a progress bar in your iPhone UINavigationBar 22 Aug 2011

Hacker News Reader 20 Aug 2011

How to programmatically resize elements for landscape vs portrait in your iphone interface 16 Aug 2011

MegaComet testing part 2 3 Aug 2011

Australian Baby Colours 28 Jul 2011

Boat prop shaft 25 Jul 2011

Megacomet with 1 million queued messages 24 Jul 2011

Installed the strut and rudder 18 Jul 2011

Painted the inside of the boat 17 Jul 2011

Fuzzy iphone graphics when using an UIImageView set to UIViewContentModeCenter 13 Jul 2011

My 3 Data and Calls Usage 11 Jul 2011

Reading a line from the console in node.js 10 Jul 2011

Trim whitespaces on all text fields in a view controller 9 Jul 2011

Final finish 9 Jul 2011

MessagePack parser for Objective-C / iPhone 30 Jun 2011

Lacquering the starboard side 25 Jun 2011

What do do with EXC_ARM_DA_ALIGN on an iPhone app 23 Jun 2011

Lacquering the hull 23 Jun 2011

Staining the boat 22 Jun 2011

NSMutableSet with weak references in objective-c 20 Jun 2011

Iphone gesture recogniser that works for baby games 20 Jun 2011

Image manipulation pixel by pixel in objective C for the iphone 19 Jun 2011

Baby Allergy Tracker 12 Jun 2011

Power sanding the deck 10 Jun 2011

Planing the edge of the deck 2 Jun 2011

Figured out the deck 2 Jun 2011

Boat bulkheads 2 Jun 2011

Simulating iOS memory warnings 31 May 2011

Putting a UIButton in a UIToolbar 29 May 2011

How to allow closing a UIActionSheet by tapping outside it 29 May 2011

Finding the currently visible view in a UITabBarController 24 May 2011

Random Chef 17 May 2011

Centered UIButton in a navigation bar on the iphone 16 May 2011

Little Orchard 13 May 2011

Boat update 13 May 2011

How to get the current time in all time zones for the iphone / obj-c 12 May 2011

Design portfolio 10 May 2011

Tricks with grand central dispatch, such as objective-c's equivalent to setTimeout 9 May 2011

How to make an iphone view controller detect left or right swipes 5 May 2011

Centered section headers on a UITableView 5 May 2011

Christmas in may 4 May 2011

Finished trimming the boat (its floatable now!) and got some parts 29 Apr 2011

How to make a multiline label with dynamic text on the iphone and get the correct height 27 Apr 2011

Forcing an image size on the image in a table view cell on an iphone 20 Apr 2011

Git on the Mac 19 Apr 2011

Build a url query string in obj-c from a dictionary of params like jquery does 12 Apr 2011

Rendering a radial gradient on the iphone / objective-c 11 Apr 2011

Skinning the port side of the boat 8 Apr 2011

Skinning the side of the boat 5 Apr 2011

Sending a UDP broadcast packet in C / Objective-C 5 Apr 2011

How to talk to a unix socket / named pipe with python 4 Apr 2011

Skinning the bottom of the boat 31 Mar 2011

Service discovery using node.js and ssdp / universal plug n play 30 Mar 2011

Extremely simple python threading 29 Mar 2011

New rescue boat 26 Mar 2011

HttpContext vs HttpContextBase vs HttpContextWrapper 5 Nov 2010

Simple C# Wiki engine 30 Sep 2010

Simple way to throttle parts of your Asp.Net web app 29 Sep 2010

How to implement DES and Triple DES from scratch 4 Aug 2010

How to use sessions with Struts 2 30 Jul 2010

How to use Cookies in Struts 2 with ServletRequest and ServletResponse 30 Jul 2010

Using Quartz Scheduler in a Java web app (servlet) 27 Jul 2010

Javascript date picker that Doesn't Suck!(tm) 27 Jul 2010

Using Oracle XE with Hibernate 20 Jul 2010

A simple implementation of AES in Ruby from scratch 29 Jun 2010

Asp.Net Forms authentication to your own database 28 May 2010

AS2805 (like ISO8583) financial message parser in C# 7 May 2010

Ruby hex dumper 4 May 2010

Using Spring to manage Hibernate sessions in Struts2 (and other web frameworks) 13 Jan 2010

Emails in C#: Delivery and Read receipts / Attachments 12 Jan 2010

Using Java libraries in a C# app with IKVM 16 Dec 2009

Learning Java tutorial 27 Nov 2009

Using generic database providers with C# 17 Nov 2009

Scheduled task executable batch babysitter 29 Oct 2009

Working with query strings in Javascript using Prototype 30 Sep 2009

Still fighting with String.Format? 9 Sep 2009

How I'd build the next Google 24 Aug 2009

Getting IIS and Tomcat to play nicely with isapi_redirect 24 Aug 2009

Using the new ODP.Net to access Oracle from C# with simple deployment 11 Aug 2009

C# Cryptography - Encrypting a bunch of bytes 14 Jul 2009

Sorting enormous files using a C# external merge sort 10 Jul 2009

Reconciling/comparing huge data sets with C# 9 Jul 2009

Some keyboard-friendly DHTML tricks 10 Jun 2009

How to figure out what/who is connected to your SQL server 18 Mar 2009

Adding a column to a massive Sql server table 16 Mar 2009

Multithreading using Delegates in C# 10 Mar 2009

Using C# locks and threads to rip through a to-do list 6 Feb 2009

Using threads and lock in C# 3 Feb 2009

Compressing using the 7Zip LZMA algorithm in C# beats GZipStream 14 Jan 2009

MS Sql Server 2005 locking 17 Dec 2008

Simple Comet demo for Ruby on Rails 19 Nov 2008

Geocoding part 2 - Plotting postcodes onto a map of Australia with C# 24 Oct 2008

Using evolutionary algorithms to make a walkthrough for the light-bot game with C# 20 Oct 2008

How to tell when memory leaks are about to kill your Asp.Net application 16 Oct 2008

C# version of isxdigit - is a character a hex digit? 15 Sep 2008

Geocoding part 1 - Getting the longitude and latitude of all australian postcodes from google maps 26 Aug 2008

Converting HSV to RGB colour using C# 14 Aug 2008

Opening a TCP connection in C# with a custom timeout 11 Aug 2008

Oracle Explorer - a very simple C# open source Toad alternative 31 Jul 2008

Linking DigitalMars' D with a C library (Mongrel's HTTP parser) 23 Jun 2008

Connecting to Oracle from C# / Winforms / without tnsnames.ora 16 Jun 2008

A simple server: DigitalMars' D + Libev 6 Jun 2008

Travelling from Rails 1 to Rails 2 9 Apr 2008

Online Rostering System 9 Apr 2008

DanceInforma 9 Apr 2008

Using RSS or Atom to keep an eye on your company's heartbeat 10 Nov 2007

Easy Integrated Active Directory Security in ASP.Net 24 Oct 2007