微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何在应用中实现多个 CBPeripherals

如何解决如何在应用中实现多个 CBPeripherals

我正在编写一个适用于蓝牙设备的应用程序。起初,只有一个设备,所有应用程序逻辑都严格围绕它构建(ViewController 和 Model)。但是,设备列表会增长,我需要实现代码的良好可扩展性。事实是所有设备都遵循单一协议,即所有设备的服务和特性都是相同的,只是一些设备支持某些东西,而另一些则不支持。我立即意识到为每个设备描述一个单独的模型将是一个非常麻烦的决定,因为会有很多重复的代码。首先,我创建了一个抽象设备类BasePeripheral,它描述了基本的东西(连接、断开、扫描服务和特性),然后从它继承了为它们实现相应协议的特定设备。一般来说,这样的解决方案使任务更容易一些,但我仍然确信这远非理想。我没有足够的经验,因此我想被提示为我工作的方向。起初在我看来,解决方案可能是使用泛型,但很难立即理解它们对我的目的的工作。还有一个识别设备类型的问题,目前这是通过笨拙的 Switch-Case 设计完成的,我对它的使用不是很满意,例如,识别 50 种不同的设备。我想知道我应该学习哪些东西来实现我的目标(如果有代码示例会更好)

protocol ColorPeripheral {
    func writeColor(_ color: Int)
}

protocol AnimationPeriheral {
    func writeAnimation(_ animation: Int)
    func writeAnimationSpeed(_ speed: Int)
}

protocol distancePeripheral {
    // No write properties. Only readable
}

protocol IndicationPeripheral {
    // No write properties. Only readable
}

protocol PeripheralDataDelegate {
    func getColor(_ color: Any)
    func getAnimation(_ animation: Any)
    func getAnimationSpeed(_ speed: Any)
    func getdistance(_ distance: Any)
    func getLedState(_ state: Any)
}

class BasePeripheral: NSObject,CBCentralManagerDelegate,CBPeripheralDelegate {
    
    let centralManager: CBCentralManager
    let basePeripheral: CBPeripheral
    let advertisingData: [String : Any]
    public private(set) var advertisedname: String = "UnkNown Device".localized
    public private(set) var RSSI          : NSNumber
    public var type : BasePeripheral.Type?
    public var services: [CBUUID]?
    public var delegate: PeripheralDataDelegate?
    
    init(withPeripheral peripheral: CBPeripheral,advertisementData advertisementDictionary: [String : Any],andRSSI currentRSSI: NSNumber,using manager: CBCentralManager) {
        self.centralManager = manager
        self.basePeripheral = peripheral
        self.advertisingData = advertisementDictionary
        self.RSSI = currentRSSI
        super.init()
        self.advertisedname = getAdvertisedname(advertisementDictionary)
        self.type = getAdvertisedService(advertisementDictionary)
        self.basePeripheral.delegate = self
    }
    
    private func getAdvertisedname(_ advertisementDictionary: [String : Any]) -> String {
        var advertisedname: String
        if let name = advertisementDictionary[CBAdvertisementDataLocalNameKey] as? String {
            advertisedname = name
        } else {
            advertisedname = "UnkNown Device".localized
        }
        return advertisedname
    }
        
    // Getting device type
    private func getAdvertisedService(_ advertisementDictionary: [String : Any]) -> BasePeripheral.Type? {
        if let advUUID = advertisementDictionary[CBAdvertisementDataServiceUUIDsKey] as? [CBUUID] {
            for uuid in advUUID {
                // Getting type from [CBUUID:BasePeripheral.Type] dictionary
                if let service = UUIDs.advServices[uuid] {
                    return service
                }
            }
        }
        return nil
    }
    
    func connect() {
        centralManager.delegate = self
        centralManager.connect(basePeripheral,options: nil)
        print("Connecting to \(advertisedname)")
    }
    
    func disconnect() {
        centralManager.cancelPeripheralConnection(basePeripheral)
        print("disconnecting from \(advertisedname)")
    }
    
    func diddiscoverService(_ service:CBService) {}
    
    func diddiscoverCharacteristic(_ characteristic: CBCharacteristic) {}
    
    func didReceiveData(from characteristic: CBCharacteristic) {}
    
    // MARK: - CBCentralManagerDelegate
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        if central.state != .poweredOn {
            print("Central Manager state changed to \(central.state)")
        }
    }
    
    func centralManager(_ central: CBCentralManager,didConnect peripheral: CBPeripheral) {
        print("Connected to \(self.advertisedname )")
        basePeripheral.discoverServices(services)
    }
    
    func centralManager(_ central: CBCentralManager,diddisconnectPeripheral peripheral: CBPeripheral,error: Error?) {
        print("disconnected from \(self.advertisedname ) - \(String(describing: error?.localizedDescription))")
    }
    
    func peripheral(_ peripheral: CBPeripheral,diddiscoverServices error: Error?) {
        if let services = peripheral.services {
            for service in services {
                diddiscoverService(service)
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral,diddiscovercharacteristicsFor service: CBService,error: Error?) {
        if let characteristics = service.characteristics {
            for characteristic in characteristics {
                diddiscoverCharacteristic(characteristic)
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral,didUpdateValueFor characteristic: CBCharacteristic,error: Error?) {
        didReceiveData(from: characteristic)
    }

}

class FirstPeripheral: BasePeripheral,ColorPeripheral,AnimationPeriheral {
    
    private var colorCharacteristic         : CBCharacteristic?
    private var animationCharacteristic     : CBCharacteristic?
    private var animationSpeedCharacteristic: CBCharacteristic?

    init(superPeripheral: BasePeripheral) {
        super.init(withPeripheral:    superPeripheral.basePeripheral,advertisementData: superPeripheral.advertisingData,andRSSI:           superPeripheral.RSSI,using:             superPeripheral.centralManager)
        super.services = [UUIDs.COLOR_SERVICE_UUID,UUIDs.ANIMATION_SERVICE_UUID]
    }
    
    // ColorPeripheral protocol
    func writeColor(_ color: Int) {
        if let characteristic = colorCharacteristic {
            var value = UInt8(color)
            let data = Data(bytes: &value,count: 1)
            basePeripheral.writeValue(data,for: characteristic,type: .withoutResponse)
        }
    }
    
    // AnimationPeripheral protocol
    func writeAnimation(_ animation: Int) {
        if let characteristic = animationCharacteristic {
            var value = UInt8(animation)
            let data = Data(bytes: &value,type: .withoutResponse)
        }
    }
    
    func writeAnimationSpeed(_ speed: Int) {
        if let characteristic = animationSpeedCharacteristic {
            var value = UInt8(speed)
            let data = Data(bytes: &value,type: .withoutResponse)
        }
    }
    
    override func didReceiveData(from characteristic: CBCharacteristic) {
        switch characteristic {
        case colorCharacteristic:
            delegate?.getColor(characteristic.value!)
            break
        case animationCharacteristic:
            delegate?.getAnimation(characteristic.value!)
            break
        case animationSpeedCharacteristic:
            delegate?.getAnimationSpeed(characteristic.value!)
            break
        default:
            print("UnkNown value changed")
        }
    }
    
    override func diddiscoverService(_ service: CBService) {
        switch service.uuid {
        case UUIDs.COLOR_SERVICE_UUID:
            // discover color characteristics
            break
        case UUIDs.ANIMATION_SERVICE_UUID:
            // discover animation characteristics
            break
        default:
            print("UnkNown service found - \(service.uuid)")
        }
    }
    
    override func diddiscoverCharacteristic(_ characteristic: CBCharacteristic) {
        switch characteristic.uuid {
        case UUIDs.COLOR_PRIMARY_CHaraCTERISTIC_UUID:
            colorCharacteristic = characteristic
            break
        case UUIDs.ANIMATION_MODE_CHaraCTERISTIC_UUID:
            animationCharacteristic = characteristic
            break
        case UUIDs.ANIMATION_ON_SPEED_CHaraCTERISTIC_UUID:
            animationSpeedCharacteristic = characteristic
            break
        default:
            print("UnkNown characteristic found \(characteristic.uuid)")
        }
    }
    
}

class SecondPeripheral: BasePeripheral,distancePeripheral,IndicationPeripheral {
    
    private var colorCharacteristic      : CBCharacteristic?
    private var distanceCharacteristic   : CBCharacteristic?
    private var indicationCharacteristic : CBCharacteristic?
        
    init(superPeripheral: BasePeripheral) {
        super.init(withPeripheral:    superPeripheral.basePeripheral,UUIDs.disTANCE_SERVICE_UUID,UUIDs.INDICATION_SERVICE_UUID]
    }
    
    // ColorPeripheral protocol
    func writeColor(_ color: Int) {
        if let characteristic = colorCharacteristic {
            var value = UInt8(color)
            let data = Data(bytes: &value,type: .withoutResponse)
        }
    }
    
    override func didReceiveData(from characteristic: CBCharacteristic) {
        switch characteristic {
        case colorCharacteristic:
            delegate?.getColor(characteristic.value!)
            break
        case distanceCharacteristic:
            delegate?.getdistance(characteristic.value!)
            break
        case indicationCharacteristic:
            delegate?.getLedState(characteristic.value!)
            break
        default:
            print("UnkNown value changed")
        }
    }
    
    override func diddiscoverService(_ service: CBService) {
        switch service.uuid {
        case UUIDs.COLOR_SERVICE_UUID:
            // discover color characteristics
            break
        case UUIDs.disTANCE_SERVICE_UUID:
            // discover distance characteristics
            break
        case UUIDs.INDICATION_SERVICE_UUID:
            // discover indication characteristics
        default:
            print("UnkNown service found - \(service.uuid)")
        }
    }
    
    override func diddiscoverCharacteristic(_ characteristic: CBCharacteristic) {
        switch characteristic.uuid {
        case UUIDs.COLOR_PRIMARY_CHaraCTERISTIC_UUID:
            colorCharacteristic = characteristic
            break
        case UUIDs.disTANCE_CHaraCTERISTIC_UUID:
            distanceCharacteristic = characteristic
            break
        case UUIDs.INDICATION_CHaraCTERISTIC_UUID:
            indicationCharacteristic = characteristic
        default:
            print("UnkNown characteristic found \(characteristic.uuid)")
        }
    }
    
}

解决方法

这是非常具体的案例,但进入协议方向可能是一个很好的方法。但是你必须经常使用协议扩展。举个简单的例子:

您应该将最基础的结构声明为协议。正如您所说,它们都连接到蓝牙,并且它们都具有您希望使用的相同特征。所以:

protocol BasicPeripheralInterface {
    
    var peripheral: CBPeripheral { get }
    var firstCharacteristic: CBCharacteristic { get }
    var secondCharacteristic: CBCharacteristic { get }
    
}

现在您希望在其上公开一些接口,这些接口可能会根据设备类型而变化。例如,让我们向它发送一些颜色:

protocol ColorPeripheralInterface {
    func setColor(_ color: UIColor)
}

目前没有什么大不了的。但是现在我们可以像这样通过协议的扩展将两者连接起来:

extension ColorPeripheralInterface where Self:BasicPeripheralInterface {
    
    func setColor(_ color: UIColor) {
        peripheral.writeValue(color.toData(),for: firstCharacteristic,type: .withoutResponse)
    }
    
}

通过这样做,我们是说每个 BasicPeripheralInterfaceColorPeripheralInterface 对象都已经具有扩展中描述的逻辑。所以要创建这样一个对象,我们会做一些类似的事情

class MyDevice: BasicPeripheralInterface,ColorPeripheralInterface {
    
    let peripheral: CBPeripheral
    let firstCharacteristic: CBCharacteristic
    let secondCharacteristic: CBCharacteristic
    
    init(peripheral: CBPeripheral,firstCharacteristic: CBCharacteristic,secondCharacteristic: CBCharacteristic) {
        self.peripheral = peripheral
        self.firstCharacteristic = firstCharacteristic
        self.secondCharacteristic = secondCharacteristic
    }
    
}

这个类现在实现了 BasicPeripheralInterface 所需的逻辑,但根本没有与 ColorPeripheralInterface 相关的逻辑。无需实现 setColor,因为此方法已在扩展中。以下代码按预期工作:

func sendColor(toDevice device: MyDevice) {
    device.setColor(.red)
}

现在,当我们添加更多协议和更多设备时,我们会期待这样的事情:

protocol ColorPeripheralInterface {
    func setColor(_ color: UIColor)
}

extension ColorPeripheralInterface where Self:BasicPeripheralInterface {
    
    func setColor(_ color: UIColor) {
        peripheral.writeValue(color.toData(),type: .withoutResponse)
    }
    
}

protocol StringPeripheralInterface {
    func setText(_ text: String)
}

extension StringPeripheralInterface where Self:BasicPeripheralInterface {
    
    func setText(_ text: String) {
        peripheral.writeValue(text.data(using: .utf8)!,for: secondCharacteristic,type: .withoutResponse)
    }
    
}

protocol AmountPeripheralInterface {
    func setAmount1(_ value: Int)
    func setAmount2(_ value: Int)
}

extension AmountPeripheralInterface where Self:BasicPeripheralInterface {
    
    func setAmount1(_ value: Int) {
        peripheral.writeValue("\(value)".data(using: .utf8)!,type: .withoutResponse)
    }
    func setAmount2(_ value: Int) {
        peripheral.writeValue("\(value)".data(using: .utf8)!,type: .withoutResponse)
    }
    
}

现在这只是定义了另外 3 个扩展一些愚蠢逻辑的协议。但是现在,这些协议可以应用于并定义您喜欢的任何设备,并以您需要的任何组合方式。首先一个基类是有意义的

class BasicDevice: BasicPeripheralInterface {
    
    let peripheral: CBPeripheral
    let firstCharacteristic: CBCharacteristic
    let secondCharacteristic: CBCharacteristic
    
    init(peripheral: CBPeripheral,secondCharacteristic: CBCharacteristic) {
        self.peripheral = peripheral
        self.firstCharacteristic = firstCharacteristic
        self.secondCharacteristic = secondCharacteristic
    }
    
}

这个不会直接用。但神奇之处在于声明具体的设备:

class StringDevice: BasicDevice,StringPeripheralInterface {  }
class AmountStringDevice: BasicDevice,StringPeripheralInterface,AmountPeripheralInterface {  }
class LatestAllSupportingDevice: BasicDevice,AmountPeripheralInterface,ColorPeripheralInterface {  }

所以这些是 3 个类,它们中的每一个都为不同的功能扩展了不同的接口,现在可以用作具体的设备类。它们中的每一个都只包含在分配给它们的协议中定义的方法,仅此而已。

通过这种方式,您可以轻松定义 50 个设备,其中每个设备您只需要列出它对应的所有协议,仅此而已。

如果某个设备只有一种稍微不同的方法,您可以随时覆盖它。例如:

class ThisNewDeviceThatDoesThingsADashDifferently: BasicDevice,ColorPeripheralInterface {
    
    func setAmount2(_ value: Int) {
        peripheral.writeValue("2-\(value)".data(using: .utf8)!,type: .withoutResponse)
    }
    
}

这个类现在做的一切都与 LatestAllSupportingDevice 完全一样,但覆盖了 setAmount2 以做一些不同的事情。

您还应该知道,当涉及到 where Self: 时,扩展协议不仅限于协议。您可以轻松地删除 BasicPeripheralInterface 并在任何地方使用 BasicDevice,例如 extension ColorPeripheralInterface where Self:BasicDevice

使用这种方法可以做很多事情。但最终它可能适合也可能不适合您使用。从技术上讲,对这个问题的任何回答都是基于意见的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。