1

I enabled to “Uses Bluetooth LE accessories” and “Acts as a Bluetooth LE accessory” in “Background Modes” settings, and these are added in Info.plist.
 and also I allowed my app to access “Bluetooth sharing”.



And my app does scan BLE peripherals well on foreground mode,
 but when it enter background mode, it doesn’t scan anything no more. If it enter foreground mode again, it does scan well again.

More specifically, centralManagerDidUpdateState(_ central: CBCentralManager) always does work well on any modes. but didDiscover doesn't work on background mode.

I want to BLE scanning does work well on any modes and I think I have done everything I can.

Relevant Code

BLEManager.swift

import UIKit

import CoreBluetooth


final class BLEManager: NSObject, CBCentralManagerDelegate, CBPeripheralManagerDelegate {
    static let shared: BLEManager = .init()

    var centralManager: CBCentralManager?
    var peripheralManager: CBPeripheralManager?

    var scannedPeripherals: [CBPeripheral] = .init()
    var confirmedPeripheralStates: [String: CBPeripheralState] = .init()

    override init() {
        super.init()

        self.centralManager = .init(delegate: self, queue: nil)
        self.peripheralManager = .init(delegate: self, queue: nil)
    }

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            self.startScan()
        default:
            central.stopScan()
        }
    }

    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        print("[BLE] state: \(peripheral.state.rawValue)")
    }

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

        central.connect(peripheral)

        self.confirm(peripheral)
    }

    func startScan() {
        guard let central = self.centralManager else {
            return
        }

        let isBackgroundMode: Bool = (UIApplication.shared.applicationState == .background)
        let cbuuids: [CBUUID] = self.scannedPeripherals.map { CBUUID(nsuuid: $0.identifier) }

        central.stopScan()
        central.scanForPeripherals(withServices: isBackgroundMode ? cbuuids : nil)
    }

    func confirm(_ peripheral: CBPeripheral) {
        let uuid: String = peripheral.identifier.uuidString

        // Prevent duplicate logging.
        if peripheral.state != self.confirmedPeripheralStates[uuid] {
            self.log(peripheral)
        }

        self.confirmedPeripheralStates[uuid] = peripheral.state
    }

    func log(_ peripheral: CBPeripheral, completion: (() -> Void)? = nil) {
        guard let name = peripheral.name,
              let state = self.getPeripheralStateAsString(peripheral.state) else {
                return
        }

        print("[BLE] \(name) was \(state) on \(self.getApplicationStateAsString())")
    }
}

extension BLEManager {
    func getApplicationStateAsString() -> String {
        switch UIApplication.shared.applicationState {
        case .active:
            return "active"
        case .inactive:
            return "inactive"
        case .background:
            return "background"
        }
    }

    func getPeripheralStateAsString(_ state: CBPeripheralState) -> String? {
        switch state {
        case .disconnected:
            return "disconnected"
        case .connecting:
            return "connecting"
        case .connected:
            return "connected"
        default:
            return nil
        }
    }
}


extension Array where Element: Equatable {
    mutating func appendAsUnique(_ element: Element) {
        if let index = self.index(where: { $0 == element }) {
            self.remove(at: index)
        }

        self.append(element)
    }
}

AppDelegate.swift

func applicationDidEnterBackground(_ application: UIApplication) {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.

    BLEManager.shared.startScan()
}

Did I miss anything?

Byoth
  • 1,905
  • 6
  • 16
  • 28
  • You should edit your question to show the relevant code, but my first question is which object holds the reference to your `CBCentralManager` and acts as your delegate? Are you sure that that object isn't being released? What scan parameters are you using? – Paulw11 Sep 19 '18 at 09:00
  • Your question appears to be answered here: https://stackoverflow.com/questions/31631772/background-scanning-for-ble-in-swift – ehmjaysee Sep 19 '18 at 15:30
  • @Paulw11 I've updated this question by reference to your comment. *It's still not working on background mode.* Could you please help me again? – Byoth Sep 20 '18 at 03:28
  • Where do you add the service identifiers into `scannedPeripherals`? Also, why are you adding an extension to `Array` when you could just use a `Set`? Also, that `.init()` syntax is unusual; it is typically best to stick with conventional usage for the sake of clarity. – Paulw11 Sep 20 '18 at 10:00
  • @Paulw11 `self.scannedPeripherals.appendAsUnique(peripheral)` `self.scannedPeripherals.map { CBUUID(nsuuid: $0.identifier) }` / I have to scan several peripherals, so I need an array of service uuids for background mode. Did I get it wrong? (`isBackgroundMode ? cbuuids : nil`) / I thought it may be good for unity, but then I agree your comment now. but it's not important about this issue. – Byoth Sep 21 '18 at 07:48
  • Yes, you scan for services, not peripheral identifiers. If you already know the peripheral identifier then there is no need to scan. – Paulw11 Sep 21 '18 at 07:51
  • It is the array of services that you are interested in. You will be notified when a new peripheral that is advertising one of these services is discovered – Paulw11 Sep 21 '18 at 09:48

0 Answers0