CoreBluetooth Functions not working from Singleton

2020-07-25 10:35发布

问题:

So I currently got a bluetooth connection setup between a iPad and iPhone. I've created my testcode in the ViewController and everything works fine. Now I moved it to 2 manager classes one for the CBCentralManager and one for the CBPeripheralManager above those to classes I made a BluetoothManager which is a singleton class and holds some information regarding currently connected devices.

However when doing this I'm facing a problem it seems like the centralManager.connect() call doesn't actually work. I debugged my entire code and after that line nothing seems to happen and I can't seem to figure out why this is or where I'm actually going wrong.

The CentralManager class

import Foundation
import CoreBluetooth

class CentralManager: NSObject {
    private var centralManager: CBCentralManager!
    var peripherals: [CBPeripheral] = []

    override init() {
        super.init()

        centralManager = CBCentralManager(delegate: self, queue: DispatchQueue.main)
    }
}

// MARK: - CBCentralManager Delegate Methods
extension CentralManager: CBCentralManagerDelegate {

    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            centralManager.scanForPeripherals(withServices: [BLEConstants.serviceUUID], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
        default:
            break
        }
    }

    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if !peripherals.contains(peripheral) {
            peripheral.delegate = self
            peripherals.append(peripheral)
            centralManager.connect(peripheral, options: nil)
        }
    }

    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        peripheral.discoverServices([BLEConstants.serviceUUID])
    }

    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        guard let peripheralIndex = peripherals.index(of: peripheral), BluetoothManager.shared.deviceCharacteristic[peripheral] != nil else { return }

        peripherals.remove(at: peripheralIndex)
        BluetoothManager.shared.deviceCharacteristic.removeValue(forKey: peripheral)
    }

}

// MARK: - CBPeripheral Delegate Methods
extension CentralManager: CBPeripheralDelegate {

    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        for service in peripheral.services! {
            if service.uuid == BLEConstants.serviceUUID {
                peripheral.discoverCharacteristics(nil, for: service)
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        for characteristic in service.characteristics! {
            let characteristic = characteristic as CBCharacteristic

            if BluetoothManager.shared.deviceCharacteristic[peripheral] == nil {
                BluetoothManager.shared.deviceCharacteristic[peripheral] = characteristic
            }
        }
    }

    func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {

    }

}

The PeripheralManager class

class PeripheralManager: NSObject {
    private var peripheralManager: CBPeripheralManager!

    override init() {
        super.init()

        peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
    }

}

// MARK: - Manage Methods
extension PeripheralManager {

    func updateAdvertising() {
        guard !peripheralManager.isAdvertising else { peripheralManager.stopAdvertising(); return }

        let advertisingData: [String: Any] = [CBAdvertisementDataServiceUUIDsKey: BLEConstants.serviceUUID,
                               CBAdvertisementDataLocalNameKey: BLEConstants.bleAdvertisementKey]
        peripheralManager.startAdvertising(advertisingData)
    }

    func initializeService() {
        let service = CBMutableService(type: BLEConstants.serviceUUID, primary: true)

        let characteristic = CBMutableCharacteristic(type: BLEConstants.charUUID, properties: BLEConstants.charProperties, value: nil, permissions: BLEConstants.charPermissions)
        service.characteristics = [characteristic]

        peripheralManager.add(service)
    }

}

// MARK: - CBPeripheralManager Delegate Methods
extension PeripheralManager: CBPeripheralManagerDelegate {

    func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) {
        if peripheral.state == .poweredOn {
            initializeService()
            updateAdvertising()
        }
    }

    func peripheralManager(_ peripheral: CBPeripheralManager, didReceiveWrite requests: [CBATTRequest]) {
        for request in requests {
            if let value = request.value {
                let messageText = String(data: value, encoding: String.Encoding.utf8)
                print(messageText ?? "")
            }
            self.peripheralManager.respond(to: request, withResult: .success)
        }
    }

}

The BluetoothManager class

class BluetoothManager {
    static let shared = BluetoothManager()
    private var centralManager: CentralManager!
    private var peripheralManager: PeripheralManager!

    var deviceCharacteristic: [CBPeripheral: CBCharacteristic] = [:]
    var connectedPeripherals: [CBPeripheral] { return centralManager.peripherals }

    func setup() {
        centralManager = CentralManager()
        peripheralManager = PeripheralManager()
    }

}

and then in my ViewController didLoad I'm calling BluetoothManager.shared.setup()

Does anyone know why the devices don't seem to connect with eachother or maybe the delegate functions after that just don't get called?

回答1:

The process starts when the static shared variable is initialized with BluetoothManager(). I am not sure when this happens in Swift, it is either at the very start of the program, or when you use BluetoothManager.setup for the first time. Initialization of the variable calls the init() method of the BluetoothManager. This will instantiate a CentralManager, and its init() method will be called. This will instantiate a CBCentralManager, which will start the Bluetooth process.

Then you call setup(), which will instantiate a new CentralManager, with its own CBCentralManager. I can imagine something goes wrong with two CBCentralManager.

To solve it, don't use setup(), but initialize the variables in init() instead.

To debug this kind of situation, put breakpoints in all init() methods. Create destructors, and put breakpoints in those as well. Technically, you need destructors anyways, because you need to remove yourself as delegate from the CBCentralManager object.


Note also that you only call scanForPeripherals from centralManagerDidUpdateState. The CBCentralManager can already be in poweredOn state when it starts, this can happen when another app is using Bluetooth at the same time - or when your first CBCentralManager object has already started it. In that case, centralManagerDidUpdateState will never be called.



回答2:

Are you sure your Singleton is correctly initialized?

Try this:

import Foundation

private let singleton = Singleton()

class Singleton {

  static let sharedInstance : Singleton = {
    return singleton
  }()

  let cnetralManager = = CBCentralManager(delegate: self, queue: DispatchQueue.main)
}