diff --git a/Sources/iOS-BLE-Library-Mock/Alias.swift b/Sources/iOS-BLE-Library-Mock/Alias.swift index b04c7c5..3f8811d 100644 --- a/Sources/iOS-BLE-Library-Mock/Alias.swift +++ b/Sources/iOS-BLE-Library-Mock/Alias.swift @@ -38,50 +38,59 @@ import CoreBluetoothMock // disabled for Xcode 12.5 beta //typealias CBPeer = CBMPeer //typealias CBAttribute = CBMAttribute -public typealias CBCentralManagerFactory = CBMCentralManagerFactory -public typealias CBUUID = CBMUUID -public typealias CBError = CBMError -public typealias CBATTError = CBMATTError -public typealias CBManagerState = CBMManagerState -public typealias CBPeripheralState = CBMPeripheralState -public typealias CBCentralManager = CBMCentralManager -public typealias CBCentralManagerDelegate = CBMCentralManagerDelegate -public typealias CBPeripheral = CBMPeripheral -public typealias CBPeripheralDelegate = CBMPeripheralDelegate -public typealias CBService = CBMService -public typealias CBCharacteristic = CBMCharacteristic -public typealias CBCharacteristicWriteType = CBMCharacteristicWriteType -public typealias CBCharacteristicProperties = CBMCharacteristicProperties -public typealias CBDescriptor = CBMDescriptor -public typealias CBConnectionEvent = CBMConnectionEvent +public typealias CBCentralManagerFactory = CBMCentralManagerFactory +public typealias CBUUID = CBMUUID +public typealias CBError = CBMError +public typealias CBATTError = CBMATTError +public typealias CBManagerState = CBMManagerState +public typealias CBPeripheralState = CBMPeripheralState +public typealias CBCentralManager = CBMCentralManager +public typealias CBCentralManagerDelegate = CBMCentralManagerDelegate +public typealias CBPeripheral = CBMPeripheral +public typealias CBPeripheralDelegate = CBMPeripheralDelegate +public typealias CBService = CBMService +public typealias CBCharacteristic = CBMCharacteristic +public typealias CBCharacteristicWriteType = CBMCharacteristicWriteType +public typealias CBCharacteristicProperties = CBMCharacteristicProperties +public typealias CBDescriptor = CBMDescriptor +public typealias CBConnectionEvent = CBMConnectionEvent public typealias CBConnectionEventMatchingOption = CBMConnectionEventMatchingOption @available(iOS 11.0, tvOS 11.0, watchOS 4.0, *) -public typealias CBL2CAPPSM = CBML2CAPPSM +public typealias CBL2CAPPSM = CBML2CAPPSM @available(iOS 11.0, tvOS 11.0, watchOS 4.0, *) -public typealias CBL2CAPChannel = CBML2CAPChannel +public typealias CBL2CAPChannel = CBML2CAPChannel -public let CBCentralManagerScanOptionAllowDuplicatesKey = CBMCentralManagerScanOptionAllowDuplicatesKey -public let CBCentralManagerOptionShowPowerAlertKey = CBMCentralManagerOptionShowPowerAlertKey -public let CBCentralManagerOptionRestoreIdentifierKey = CBMCentralManagerOptionRestoreIdentifierKey -public let CBCentralManagerScanOptionSolicitedServiceUUIDsKey = CBMCentralManagerScanOptionSolicitedServiceUUIDsKey -public let CBConnectPeripheralOptionStartDelayKey = CBMConnectPeripheralOptionStartDelayKey +public let CBCentralManagerScanOptionAllowDuplicatesKey = + CBMCentralManagerScanOptionAllowDuplicatesKey +public let CBCentralManagerOptionShowPowerAlertKey = CBMCentralManagerOptionShowPowerAlertKey +public let CBCentralManagerOptionRestoreIdentifierKey = CBMCentralManagerOptionRestoreIdentifierKey +public let CBCentralManagerScanOptionSolicitedServiceUUIDsKey = + CBMCentralManagerScanOptionSolicitedServiceUUIDsKey +public let CBConnectPeripheralOptionStartDelayKey = CBMConnectPeripheralOptionStartDelayKey #if !os(macOS) -@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) -public let CBConnectPeripheralOptionRequiresANCS = CBMConnectPeripheralOptionRequiresANCS + @available(iOS 13.0, tvOS 13.0, watchOS 6.0, *) + public let CBConnectPeripheralOptionRequiresANCS = CBMConnectPeripheralOptionRequiresANCS #endif -public let CBCentralManagerRestoredStatePeripheralsKey = CBMCentralManagerRestoredStatePeripheralsKey -public let CBCentralManagerRestoredStateScanServicesKey = CBMCentralManagerRestoredStateScanServicesKey -public let CBCentralManagerRestoredStateScanOptionsKey = CBMCentralManagerRestoredStateScanOptionsKey +public let CBCentralManagerRestoredStatePeripheralsKey = + CBMCentralManagerRestoredStatePeripheralsKey +public let CBCentralManagerRestoredStateScanServicesKey = + CBMCentralManagerRestoredStateScanServicesKey +public let CBCentralManagerRestoredStateScanOptionsKey = + CBMCentralManagerRestoredStateScanOptionsKey -public let CBAdvertisementDataLocalNameKey = CBMAdvertisementDataLocalNameKey -public let CBAdvertisementDataServiceUUIDsKey = CBMAdvertisementDataServiceUUIDsKey -public let CBAdvertisementDataIsConnectable = CBMAdvertisementDataIsConnectable -public let CBAdvertisementDataTxPowerLevelKey = CBMAdvertisementDataTxPowerLevelKey -public let CBAdvertisementDataServiceDataKey = CBMAdvertisementDataServiceDataKey -public let CBAdvertisementDataManufacturerDataKey = CBMAdvertisementDataManufacturerDataKey -public let CBAdvertisementDataOverflowServiceUUIDsKey = CBMAdvertisementDataOverflowServiceUUIDsKey -public let CBAdvertisementDataSolicitedServiceUUIDsKey = CBMAdvertisementDataSolicitedServiceUUIDsKey +public let CBAdvertisementDataLocalNameKey = CBMAdvertisementDataLocalNameKey +public let CBAdvertisementDataServiceUUIDsKey = CBMAdvertisementDataServiceUUIDsKey +public let CBAdvertisementDataIsConnectable = CBMAdvertisementDataIsConnectable +public let CBAdvertisementDataTxPowerLevelKey = CBMAdvertisementDataTxPowerLevelKey +public let CBAdvertisementDataServiceDataKey = CBMAdvertisementDataServiceDataKey +public let CBAdvertisementDataManufacturerDataKey = CBMAdvertisementDataManufacturerDataKey +public let CBAdvertisementDataOverflowServiceUUIDsKey = CBMAdvertisementDataOverflowServiceUUIDsKey +public let CBAdvertisementDataSolicitedServiceUUIDsKey = + CBMAdvertisementDataSolicitedServiceUUIDsKey -public let CBConnectPeripheralOptionNotifyOnConnectionKey = CBMConnectPeripheralOptionNotifyOnConnectionKey -public let CBConnectPeripheralOptionNotifyOnDisconnectionKey = CBMConnectPeripheralOptionNotifyOnDisconnectionKey -public let CBConnectPeripheralOptionNotifyOnNotificationKey = CBMConnectPeripheralOptionNotifyOnNotificationKey \ No newline at end of file +public let CBConnectPeripheralOptionNotifyOnConnectionKey = + CBMConnectPeripheralOptionNotifyOnConnectionKey +public let CBConnectPeripheralOptionNotifyOnDisconnectionKey = + CBMConnectPeripheralOptionNotifyOnDisconnectionKey +public let CBConnectPeripheralOptionNotifyOnNotificationKey = + CBMConnectPeripheralOptionNotifyOnNotificationKey diff --git a/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift b/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift index 87dd282..185f6e4 100644 --- a/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift +++ b/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift @@ -18,7 +18,8 @@ extension CentralManager { public var localizedDescription: String { switch self { case .wrongManager: - return "Incorrect manager instance provided. Delegate must be of type ReactiveCentralManagerDelegate." + return + "Incorrect manager instance provided. Delegate must be of type ReactiveCentralManagerDelegate." case .badState(let state): return "Bad state: \(state)." case .unknownError: @@ -54,7 +55,7 @@ private class Observer: NSObject { } /// A Custom Central Manager class. -/// +/// /// It wraps the standard CBCentralManager and has similar API. However, instead of using delegate, it uses publishers, thus bringing the reactive programming paradigm to the CoreBluetooth framework. public class CentralManager { private let isScanningSubject = CurrentValueSubject(false) @@ -63,7 +64,7 @@ public class CentralManager { /// The underlying CBCentralManager instance. public let centralManager: CBCentralManager - + /// The reactive delegate for the ``centralManager``. public let centralManagerDelegate: ReactiveCentralManagerDelegate @@ -73,10 +74,12 @@ public class CentralManager { /// - queue: The queue to perform operations on. Default is the main queue. public init( centralManagerDelegate: ReactiveCentralManagerDelegate = - ReactiveCentralManagerDelegate(), queue: DispatchQueue = .main, options: [String : Any]? = nil + ReactiveCentralManagerDelegate(), queue: DispatchQueue = .main, + options: [String: Any]? = nil ) { self.centralManagerDelegate = centralManagerDelegate -self.centralManager = CBMCentralManagerFactory.instance(delegate: centralManagerDelegate, queue: queue) + self.centralManager = CBMCentralManagerFactory.instance( + delegate: centralManagerDelegate, queue: queue) observer.setup() } @@ -109,17 +112,17 @@ extension CentralManager { /// If the peripheral was disconnected successfully, the publisher finishes without error. /// If the connection was unsuccessful or disconnection returns an error (e.g., peripheral disconnected unexpectedly), /// the publisher finishes with an error. - /// - /// Use ``CentralManager/connect(_:options:)`` to connect to a peripheral. - /// The returned publisher will emit the connected peripheral or an error if the connection fails. - /// The publisher will not complete until the peripheral is disconnected. - /// If the connection fails, or the peripheral is unexpectedly disconnected, the publisher will fail with an error. - /// - /// ```swift - /// centralManager.connect(peripheral) - /// .sink { completion in - /// switch completion { - /// case .finished: + /// + /// Use ``CentralManager/connect(_:options:)`` to connect to a peripheral. + /// The returned publisher will emit the connected peripheral or an error if the connection fails. + /// The publisher will not complete until the peripheral is disconnected. + /// If the connection fails, or the peripheral is unexpectedly disconnected, the publisher will fail with an error. + /// + /// ```swift + /// centralManager.connect(peripheral) + /// .sink { completion in + /// switch completion { + /// case .finished: /// print("Peripheral disconnected successfully") /// case .failure(let error): /// print("Error: \(error)") @@ -152,15 +155,16 @@ extension CentralManager { .bluetooth { self.centralManager.connect(peripheral, options: options) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } /// Cancels the connection with the specified peripheral. /// - Parameter peripheral: The peripheral to disconnect from. /// - Returns: A publisher that emits the disconnected peripheral. - public func cancelPeripheralConnection(_ peripheral: CBPeripheral) -> AnyPublisher - { + public func cancelPeripheralConnection(_ peripheral: CBPeripheral) -> AnyPublisher< + CBPeripheral, Error + > { return self.disconnectedPeripheralsChannel .tryFilter { r in guard r.0.identifier == peripheral.identifier else { @@ -175,17 +179,17 @@ extension CentralManager { } .map { $0.0 } .first() - .bluetooth { - self.centralManager.cancelPeripheralConnection(peripheral) - } - .autoconnect() - .eraseToAnyPublisher() + .bluetooth { + self.centralManager.cancelPeripheralConnection(peripheral) + } + .autoconnect() + .eraseToAnyPublisher() } } // MARK: Retrieving Lists of Peripherals extension CentralManager { - #warning("check `connect` method") + #warning("check `connect` method") /// Returns a list of the peripherals connected to the system whose /// services match a given set of criteria. /// @@ -218,9 +222,9 @@ extension CentralManager { extension CentralManager { #warning("Question: Should we throw an error if the scan is already running?") /// Initiates a scan for peripherals with the specified services. - /// + /// /// Calling this method stops an ongoing scan if it is already running and finishes the publisher returned by ``scanForPeripherals(withServices:)``. - /// + /// /// - Parameter services: The services to scan for. /// - Returns: A publisher that emits scan results or an error. public func scanForPeripherals(withServices services: [CBUUID]?) @@ -250,8 +254,8 @@ extension CentralManager { .bluetooth { self.centralManager.scanForPeripherals(withServices: services) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } /// Stops an ongoing scan for peripherals. diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral+Writer.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral+Writer.swift index 52ae97a..82d7cf0 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral+Writer.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral+Writer.swift @@ -43,30 +43,30 @@ extension Peripheral { super.init(peripheral: peripheral) } } - - class DescriptorWriter: OperationQueue { - let writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> - - init( - writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, - peripheral: CBPeripheral - ) { - self.writtenEventsPublisher = writtenEventsPublisher - super.init(peripheral: peripheral) - } - } - - class DescriptorReader: OperationQueue { - let updateEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> - - init( - updateEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, - peripheral: CBPeripheral - ) { - self.updateEventsPublisher = updateEventsPublisher - super.init(peripheral: peripheral) - } - } + + class DescriptorWriter: OperationQueue { + let writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + + init( + writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + peripheral: CBPeripheral + ) { + self.writtenEventsPublisher = writtenEventsPublisher + super.init(peripheral: peripheral) + } + } + + class DescriptorReader: OperationQueue { + let updateEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + + init( + updateEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + peripheral: CBPeripheral + ) { + self.updateEventsPublisher = updateEventsPublisher + super.init(peripheral: peripheral) + } + } } extension Peripheral.CharacteristicWriter { @@ -98,32 +98,32 @@ extension Peripheral.CharacteristicReader { } extension Peripheral.DescriptorWriter { - func write(_ value: Data, to dsecriptor: CBDescriptor) -> Future { - let operation = WriteDescriptorOperation( - data: value, - writtenEventsPublisher: writtenEventsPublisher, - descriptor: dsecriptor, - peripheral: peripheral - ) - - queue.addOperation(operation) - - return operation.future - } + func write(_ value: Data, to dsecriptor: CBDescriptor) -> Future { + let operation = WriteDescriptorOperation( + data: value, + writtenEventsPublisher: writtenEventsPublisher, + descriptor: dsecriptor, + peripheral: peripheral + ) + + queue.addOperation(operation) + + return operation.future + } } extension Peripheral.DescriptorReader { - func readValue(from descriptor: CBDescriptor) -> Future { - let operation = ReadDescriptorOperation( - updateEventPublisher: updateEventsPublisher, - descriptor: descriptor, - peripheral: peripheral - ) + func readValue(from descriptor: CBDescriptor) -> Future { + let operation = ReadDescriptorOperation( + updateEventPublisher: updateEventsPublisher, + descriptor: descriptor, + peripheral: peripheral + ) - queue.addOperation(operation) + queue.addOperation(operation) - return operation.future - } + return operation.future + } } private class BasicOperation: Operation { @@ -279,101 +279,109 @@ private class ReadCharacteristicOperation: BasicOperation { private class WriteDescriptorOperation: BasicOperation { - let writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> - let descriptor: CBDescriptor - - let data: Data - - init( - data: Data, writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, - descriptor: CBDescriptor, peripheral: CBPeripheral - ) { - self.data = data - self.writtenEventsPublisher = writtenEventsPublisher - self.descriptor = descriptor - super.init(peripheral: peripheral) - } - - override func main() { - peripheral.writeValue(data, for: descriptor) - } - - override func start() { - if isCancelled { - state = .finished - return - } - - self.cancelable = writtenEventsPublisher.share() - .filter { $0.0.uuid == self.descriptor.uuid && $0.0.characteristic?.uuid == self.descriptor.characteristic?.uuid } - .first() - .tryMap { v in - if let e = v.1 { - throw e - } else { - return v.0 - } - } - .sink { [unowned self] completion in - switch completion { - case .finished: - self.promise?(.success(())) - case .failure(let e): - self.promise?(.failure(e)) - } - self.state = .finished - } receiveValue: { _ in - - } - - state = .executing - main() - } + let writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + let descriptor: CBDescriptor + + let data: Data + + init( + data: Data, writtenEventsPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + descriptor: CBDescriptor, peripheral: CBPeripheral + ) { + self.data = data + self.writtenEventsPublisher = writtenEventsPublisher + self.descriptor = descriptor + super.init(peripheral: peripheral) + } + + override func main() { + peripheral.writeValue(data, for: descriptor) + } + + override func start() { + if isCancelled { + state = .finished + return + } + + self.cancelable = writtenEventsPublisher.share() + .filter { + $0.0.uuid == self.descriptor.uuid + && $0.0.characteristic?.uuid + == self.descriptor.characteristic?.uuid + } + .first() + .tryMap { v in + if let e = v.1 { + throw e + } else { + return v.0 + } + } + .sink { [unowned self] completion in + switch completion { + case .finished: + self.promise?(.success(())) + case .failure(let e): + self.promise?(.failure(e)) + } + self.state = .finished + } receiveValue: { _ in + + } + + state = .executing + main() + } } private class ReadDescriptorOperation: BasicOperation { - let updateEventPublisher: AnyPublisher<(CBDescriptor, Error?), Never> - let descriptor: CBDescriptor - - init( - updateEventPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, - descriptor: CBDescriptor, peripheral: CBPeripheral - ) { - self.updateEventPublisher = updateEventPublisher - self.descriptor = descriptor - super.init(peripheral: peripheral) - } - - override func main() { - peripheral.readValue(for: descriptor) - } - - override func start() { - if isCancelled { - state = .finished - return - } - - self.cancelable = updateEventPublisher.share() - .filter { $0.0.uuid == self.descriptor.uuid && $0.0.characteristic?.uuid == self.descriptor.characteristic?.uuid } - .first() - .tryMap { v in - if let e = v.1 { - throw e - } else { - return v.0.value - } - } - .sink { [unowned self] completion in - if case .failure(let e) = completion { - self.promise?(.failure(e)) - } - self.state = .finished - } receiveValue: { v in - self.promise?(.success(v)) - } - - state = .executing - main() - } + let updateEventPublisher: AnyPublisher<(CBDescriptor, Error?), Never> + let descriptor: CBDescriptor + + init( + updateEventPublisher: AnyPublisher<(CBDescriptor, Error?), Never>, + descriptor: CBDescriptor, peripheral: CBPeripheral + ) { + self.updateEventPublisher = updateEventPublisher + self.descriptor = descriptor + super.init(peripheral: peripheral) + } + + override func main() { + peripheral.readValue(for: descriptor) + } + + override func start() { + if isCancelled { + state = .finished + return + } + + self.cancelable = updateEventPublisher.share() + .filter { + $0.0.uuid == self.descriptor.uuid + && $0.0.characteristic?.uuid + == self.descriptor.characteristic?.uuid + } + .first() + .tryMap { v in + if let e = v.1 { + throw e + } else { + return v.0.value + } + } + .sink { [unowned self] completion in + if case .failure(let e) = completion { + self.promise?(.failure(e)) + } + self.state = .finished + } receiveValue: { v in + self.promise?(.success(v)) + } + + state = .executing + main() + } } diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift index 600637b..35b2d6a 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift @@ -7,7 +7,6 @@ import Combine import CoreBluetooth - import CoreBluetoothMock import Foundation @@ -20,8 +19,8 @@ private class NativeObserver: Observer { private weak var publisher: CurrentValueSubject! private var observation: NSKeyValueObservation? - - let l = L(category: "peripheral") + + let l = L(category: "peripheral") init( peripheral: CoreBluetooth.CBPeripheral, @@ -35,42 +34,45 @@ private class NativeObserver: Observer { override func setup() { observation = peripheral.observe(\.state, options: [.new]) { [weak self] _, change in - // TODO: Check threads - guard let self else { return } - self.publisher.send(self.peripheral.state) + // TODO: Check threads + guard let self else { return } + self.publisher.send(self.peripheral.state) } } } private class MockObserver: Observer { - @objc private var peripheral: CBMPeripheralMock + @objc private var peripheral: CBMPeripheralMock - private weak var publisher: CurrentValueSubject! - private var observation: NSKeyValueObservation? - - init(peripheral: CBMPeripheralMock, publisher: CurrentValueSubject) { - self.peripheral = peripheral - self.publisher = publisher - super.init() - } + private weak var publisher: CurrentValueSubject! + private var observation: NSKeyValueObservation? - override func setup() { - observation = peripheral.observe(\.state, options: [.new]) { [weak self] _, change in - #warning("queue can be not only main") - DispatchQueue.main.async { - guard let self else { return } - self.publisher.send(self.peripheral.state) - } - } - } - } + init( + peripheral: CBMPeripheralMock, + publisher: CurrentValueSubject + ) { + self.peripheral = peripheral + self.publisher = publisher + super.init() + } + override func setup() { + observation = peripheral.observe(\.state, options: [.new]) { + [weak self] _, change in + #warning("queue can be not only main") + DispatchQueue.main.async { + guard let self else { return } + self.publisher.send(self.peripheral.state) + } + } + } +} public class Peripheral { - private var serviceDiscoveryQueue = Queue() - - let l = L(category: #file) - + private var serviceDiscoveryQueue = Queue() + + let l = L(category: #file) + /// I'm Errr from Omicron Persei 8 public enum Err: Error { case badDelegate @@ -79,10 +81,10 @@ public class Peripheral { /// The underlying CBPeripheral instance. public let peripheral: CBPeripheral - // MARK: Identifying a Peripheralin page link - /// The name of the peripheral. - public var name: String? { peripheral.name } - + // MARK: Identifying a Peripheralin page link + /// The name of the peripheral. + public var name: String? { peripheral.name } + /// The delegate for handling peripheral events. public let peripheralDelegate: ReactivePeripheralDelegate @@ -99,36 +101,43 @@ public class Peripheral { .eraseToAnyPublisher(), peripheral: peripheral ) - - private lazy var descriptorWriter = DescriptorWriter( - writtenEventsPublisher: self.peripheralDelegate.writtenDescriptorValuesSubject.eraseToAnyPublisher(), - peripheral: peripheral - ) - - private lazy var descriptorReader = DescriptorReader( - updateEventsPublisher: self.peripheralDelegate.updatedDescriptorValuesSubject.eraseToAnyPublisher(), - peripheral: peripheral - ) + + private lazy var descriptorWriter = DescriptorWriter( + writtenEventsPublisher: self.peripheralDelegate.writtenDescriptorValuesSubject + .eraseToAnyPublisher(), + peripheral: peripheral + ) + + private lazy var descriptorReader = DescriptorReader( + updateEventsPublisher: self.peripheralDelegate.updatedDescriptorValuesSubject + .eraseToAnyPublisher(), + peripheral: peripheral + ) // TODO: Why don't we use default delegate? /// Initializes a Peripheral instance. - /// - /// - Parameters: - /// - peripheral: The CBPeripheral to manage. - /// - delegate: The delegate for handling peripheral events. - public init(peripheral: CBPeripheral, delegate: ReactivePeripheralDelegate = ReactivePeripheralDelegate()) { + /// + /// - Parameters: + /// - peripheral: The CBPeripheral to manage. + /// - delegate: The delegate for handling peripheral events. + public init( + peripheral: CBPeripheral, + delegate: ReactivePeripheralDelegate = ReactivePeripheralDelegate() + ) { self.peripheral = peripheral self.peripheralDelegate = delegate - assert(peripheral.delegate == nil, "CBPeripheral's delegate should be nil, otherwise it can lead to problems") + assert( + peripheral.delegate == nil, + "CBPeripheral's delegate should be nil, otherwise it can lead to problems") peripheral.delegate = delegate -if let p = peripheral as? CBMPeripheralNative { - observer = NativeObserver(peripheral: p.peripheral, publisher: stateSubject) - observer.setup() - } else if let p = peripheral as? CBMPeripheralMock { - observer = MockObserver(peripheral: p, publisher: stateSubject) - observer.setup() - } + if let p = peripheral as? CBMPeripheralNative { + observer = NativeObserver(peripheral: p.peripheral, publisher: stateSubject) + observer.setup() + } else if let p = peripheral as? CBMPeripheralMock { + observer = MockObserver(peripheral: p, publisher: stateSubject) + observer.setup() + } } } @@ -142,168 +151,170 @@ extension Peripheral { // MARK: - Discovering Servicesin page link extension Peripheral { - /// Discover services for the peripheral. - /// - /// - Parameter serviceUUIDs: An optional array of service UUIDs to filter the discovery results. If nil, all services will be discovered. - /// - Returns: A publisher emitting discovered services or an error. - public func discoverServices(serviceUUIDs: [CBUUID]?) - -> AnyPublisher<[CBService], Error> - { - let id = UUID() - - let allServices = peripheralDelegate.discoveredServicesSubject - .first(where: { $0.id == id } ) - .tryCompactMap { result throws -> [CBService]? in - if let e = result.error { - throw e - } else { - return result.value - } - } - .first() - - return allServices.bluetooth { - let operation = IdentifiableOperation(id: id) { - self.peripheral.discoverServices(serviceUUIDs) - self.l.d("\(#function). operation ID: \(id)") - if let serviceUUIDs { - for sid in serviceUUIDs { - self.l.d("Services: \(sid)") - } - } else { - self.l.d("All services") - } - } - - self.peripheralDelegate.discoveredServicesQueue.addOperation(operation) - } - .autoconnect() - .eraseToAnyPublisher() - } - - /// Discovers the specified included services of a previously-discovered service. - public func discoverIncludedServices(_ includedServiceUUIDs: [CBUUID]?, for: CBService) -> AnyPublisher<[CBService], Error> { - fatalError() - } - - /// A list of a peripheral’s discovered services. - public var services: [CBService]? { - peripheral.services - } + /// Discover services for the peripheral. + /// + /// - Parameter serviceUUIDs: An optional array of service UUIDs to filter the discovery results. If nil, all services will be discovered. + /// - Returns: A publisher emitting discovered services or an error. + public func discoverServices(serviceUUIDs: [CBUUID]?) + -> AnyPublisher<[CBService], Error> + { + let id = UUID() + + let allServices = peripheralDelegate.discoveredServicesSubject + .first(where: { $0.id == id }) + .tryCompactMap { result throws -> [CBService]? in + if let e = result.error { + throw e + } else { + return result.value + } + } + .first() + + return allServices.bluetooth { + let operation = IdentifiableOperation(id: id) { + self.peripheral.discoverServices(serviceUUIDs) + self.l.d("\(#function). operation ID: \(id)") + if let serviceUUIDs { + for sid in serviceUUIDs { + self.l.d("Services: \(sid)") + } + } else { + self.l.d("All services") + } + } + + self.peripheralDelegate.discoveredServicesQueue.addOperation(operation) + } + .autoconnect() + .eraseToAnyPublisher() + } + + /// Discovers the specified included services of a previously-discovered service. + public func discoverIncludedServices(_ includedServiceUUIDs: [CBUUID]?, for: CBService) + -> AnyPublisher<[CBService], Error> + { + fatalError() + } + + /// A list of a peripheral’s discovered services. + public var services: [CBService]? { + peripheral.services + } } //MARK: - Discovering Characteristics and Descriptorsin page link extension Peripheral { /// Discover characteristics for a given service. - /// - /// - Parameters: - /// - characteristicUUIDs: An optional array of characteristic UUIDs to filter the discovery results. If nil, all characteristics will be discovered. - /// - service: The service for which to discover characteristics. - /// - Returns: A publisher emitting discovered characteristics or an error. + /// + /// - Parameters: + /// - characteristicUUIDs: An optional array of characteristic UUIDs to filter the discovery results. If nil, all characteristics will be discovered. + /// - service: The service for which to discover characteristics. + /// - Returns: A publisher emitting discovered characteristics or an error. public func discoverCharacteristics( _ characteristicUUIDs: [CBUUID]?, for service: CBService ) -> AnyPublisher<[CBCharacteristic], Error> { - let id = UUID() - + let id = UUID() + let allCharacteristics = peripheralDelegate.discoveredCharacteristicsSubject - .filter { - $0.value.0.uuid == service.uuid - } - .first(where: { $0.id == id } ) + .filter { + $0.value.0.uuid == service.uuid + } + .first(where: { $0.id == id }) .tryCompactMap { result throws -> [CBCharacteristic]? in - if let e = result.error { + if let e = result.error { throw e } else { - return result.value.1 + return result.value.1 } } - .first() + .first() return allCharacteristics.bluetooth { - self.peripheralDelegate.discoveredCharacteristicsQueue.enqueue(id) + self.peripheralDelegate.discoveredCharacteristicsQueue.enqueue(id) self.peripheral.discoverCharacteristics(characteristicUUIDs, for: service) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } /// Discover descriptors for a given characteristic. - /// - /// - Parameter characteristic: The characteristic for which to discover descriptors. - /// - Returns: A publisher emitting discovered descriptors or an error. + /// + /// - Parameter characteristic: The characteristic for which to discover descriptors. + /// - Returns: A publisher emitting discovered descriptors or an error. public func discoverDescriptors(for characteristic: CBCharacteristic) -> AnyPublisher<[CBDescriptor], Error> { - let id = UUID() - + let id = UUID() + return peripheralDelegate.discoveredDescriptorsSubject .filter { - $0.value.0.uuid == characteristic.uuid + $0.value.0.uuid == characteristic.uuid } - .first(where: { $0.id == id }) + .first(where: { $0.id == id }) .tryCompactMap { result throws -> [CBDescriptor]? in - if let e = result.error { + if let e = result.error { throw e } else { - return result.value.1 + return result.value.1 } } - .first() + .first() .bluetooth { - self.peripheralDelegate.discoveredDescriptorsQueue.enqueue(id) + self.peripheralDelegate.discoveredDescriptorsQueue.enqueue(id) self.peripheral.discoverDescriptors(for: characteristic) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } } // MARK: - Reading Characteristic and Descriptor Values extension Peripheral { - /// Read the value of a characteristic. - /// - /// - Parameter characteristic: The characteristic to read from. - /// - Returns: A future emitting the read data or an error. - public func readValue(for characteristic: CBCharacteristic) -> Future { - return characteristicReader.readValue(from: characteristic) - } - - /// Listen for updates to the value of a characteristic. - /// - /// - Parameter characteristic: The characteristic to monitor for updates. - /// - Returns: A publisher emitting characteristic values or an error. - public func listenValues(for characteristic: CBCharacteristic) -> AnyPublisher - { - return peripheralDelegate.updatedCharacteristicValuesSubject - .filter { $0.0.uuid == characteristic.uuid } - .tryCompactMap { (ch, err) in - if let err { - throw err - } - - return ch.value - } - .eraseToAnyPublisher() - } - - /// Read the value of a descriptor. - /// - /// - Parameter descriptor: The descriptor to read from. - /// - Returns: A future emitting the read data or an error. - public func readValue(for descriptor: CBDescriptor) -> Future { - return descriptorReader.readValue(from: descriptor) - } + /// Read the value of a characteristic. + /// + /// - Parameter characteristic: The characteristic to read from. + /// - Returns: A future emitting the read data or an error. + public func readValue(for characteristic: CBCharacteristic) -> Future { + return characteristicReader.readValue(from: characteristic) + } + + /// Listen for updates to the value of a characteristic. + /// + /// - Parameter characteristic: The characteristic to monitor for updates. + /// - Returns: A publisher emitting characteristic values or an error. + public func listenValues(for characteristic: CBCharacteristic) -> AnyPublisher + { + return peripheralDelegate.updatedCharacteristicValuesSubject + .filter { $0.0.uuid == characteristic.uuid } + .tryCompactMap { (ch, err) in + if let err { + throw err + } + + return ch.value + } + .eraseToAnyPublisher() + } + + /// Read the value of a descriptor. + /// + /// - Parameter descriptor: The descriptor to read from. + /// - Returns: A future emitting the read data or an error. + public func readValue(for descriptor: CBDescriptor) -> Future { + return descriptorReader.readValue(from: descriptor) + } } // MARK: - Writing Characteristic and Descriptor Values extension Peripheral { /// Write data to a characteristic and wait for a response. - /// - /// - Parameters: - /// - data: The data to write. - /// - characteristic: The characteristic to write to. - /// - Returns: A publisher indicating success or an error. + /// + /// - Parameters: + /// - data: The data to write. + /// - characteristic: The characteristic to write to. + /// - Returns: A publisher indicating success or an error. public func writeValueWithResponse(_ data: Data, for characteristic: CBCharacteristic) -> AnyPublisher { @@ -320,44 +331,44 @@ extension Peripheral { self.peripheral.writeValue( data, for: characteristic, type: .withResponse) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } /// Write data to a characteristic without waiting for a response. - /// - /// - Parameters: - /// - data: The data to write. - /// - characteristic: The characteristic to write to. + /// + /// - Parameters: + /// - data: The data to write. + /// - characteristic: The characteristic to write to. public func writeValueWithoutResponse(_ data: Data, for characteristic: CBCharacteristic) { peripheral.writeValue(data, for: characteristic, type: .withoutResponse) } /// Write data to a descriptor. - /// - /// - Parameters: - /// - data: The data to write. - /// - descriptor: The descriptor to write to. + /// + /// - Parameters: + /// - data: The data to write. + /// - descriptor: The descriptor to write to. public func writeValue(_ data: Data, for descriptor: CBDescriptor) -> Future { - return descriptorWriter.write(data, to: descriptor) + return descriptorWriter.write(data, to: descriptor) } } // MARK: - Setting Notifications for a Characteristic’s Value extension Peripheral { /// Set notification state for a characteristic. - /// - /// - Parameters: - /// - isEnabled: Whether notifications should be enabled or disabled. - /// - characteristic: The characteristic for which to set the notification state. - /// - Returns: A publisher indicating success or an error. + /// + /// - Parameters: + /// - isEnabled: Whether notifications should be enabled or disabled. + /// - characteristic: The characteristic for which to set the notification state. + /// - Returns: A publisher indicating success or an error. public func setNotifyValue(_ isEnabled: Bool, for characteristic: CBCharacteristic) -> AnyPublisher { if characteristic.isNotifying == isEnabled { return Just(isEnabled) .setFailureType(to: Error.self) - .eraseToAnyPublisher() + .eraseToAnyPublisher() } return peripheralDelegate.notificationStateSubject @@ -371,132 +382,140 @@ extension Peripheral { .bluetooth { self.peripheral.setNotifyValue(isEnabled, for: characteristic) } - .autoconnect() - .eraseToAnyPublisher() + .autoconnect() + .eraseToAnyPublisher() } } // MARK: - Accessing a Peripheral’s Signal Strengthin page link extension Peripheral { - /// Retrieves the current RSSI value for the peripheral while connected to the central manager. - public func readRSSI() -> AnyPublisher { - peripheralDelegate.readRSSISubject - .tryMap { rssi in - if let error = rssi.1 { - throw error - } else { - return rssi.0 - } - } - .first() - .bluetooth { - self.peripheral.readRSSI() - } - .autoconnect() - .eraseToAnyPublisher() - } + /// Retrieves the current RSSI value for the peripheral while connected to the central manager. + public func readRSSI() -> AnyPublisher { + peripheralDelegate.readRSSISubject + .tryMap { rssi in + if let error = rssi.1 { + throw error + } else { + return rssi.0 + } + } + .first() + .bluetooth { + self.peripheral.readRSSI() + } + .autoconnect() + .eraseToAnyPublisher() + } } // MARK: - Channels extension Peripheral { - /// A publisher that emits the discovered services of the peripheral. - public var discoveredServicesChannel: AnyPublisher<[CBService]?, Error> { - peripheralDelegate.discoveredServicesSubject - .tryMap { result in - if let e = result.error { - throw e - } else { - return result.value - } - } - .eraseToAnyPublisher() - } - - /// A publisher that emits the discovered characteristics of a service. - public var discoveredCharacteristicsChannel: AnyPublisher<(CBService, [CBCharacteristic]?)?, Error> { - peripheralDelegate.discoveredCharacteristicsSubject - .tryMap { result in - if let e = result.error { - throw e - } else { - return result.value - } - } - .eraseToAnyPublisher() - } - - /// A publisher that emits the discovered descriptors of a characteristic. - public var discoveredDescriptorsChannel: AnyPublisher<(CBCharacteristic, [CBDescriptor]?)?, Error> { - peripheralDelegate.discoveredDescriptorsSubject - .tryMap { result in - if let e = result.error { - throw e - } else { - return result.value - } - } - .eraseToAnyPublisher() - } - - /// A publisher that emits the updated value of a characteristic. - public var updatedCharacteristicValuesChannel: AnyPublisher<(CBCharacteristic, Error?), Never> { - peripheralDelegate.updatedCharacteristicValuesSubject - .eraseToAnyPublisher() - } - - /// A publisher that emits the updated value of a descriptor. - public var updatedDescriptorValuesChannel: AnyPublisher<(CBDescriptor, Error?), Never> { - peripheralDelegate.updatedDescriptorValuesSubject - .eraseToAnyPublisher() - } - - /// A publisher that emits the written value of a characteristic. - public var writtenCharacteristicValuesChannel: AnyPublisher<(CBCharacteristic, Error?), Never> { - peripheralDelegate.writtenCharacteristicValuesSubject - .eraseToAnyPublisher() - } - - /// A publisher that emits the written value of a descriptor. - public var writtenDescriptorValuesChannel: AnyPublisher<(CBDescriptor, Error?), Never> { - peripheralDelegate.writtenDescriptorValuesSubject - .eraseToAnyPublisher() - } - - /// A publisher that emits the notification state of a characteristic. - public var notificationStateChannel: AnyPublisher<(CBCharacteristic, Error?), Never> { - peripheralDelegate.notificationStateSubject - .eraseToAnyPublisher() - } - - /// A publisher that emits the update name of a peripheral. - public var updateNameChannel: AnyPublisher { - peripheralDelegate.updateNameSubject - .eraseToAnyPublisher() - } - - public var modifyServices: AnyPublisher<[CBService], Never> { - peripheralDelegate.modifyServicesSubject - .eraseToAnyPublisher() - } - - /// A publisher that emits the read RSSI value of a peripheral. - public var readRSSIChannel: AnyPublisher { - peripheralDelegate.readRSSISubject - .tryMap { rssi in - if let error = rssi.1 { - throw error - } else { - return rssi.0 - } - } - .eraseToAnyPublisher() - } - - /// A publisher that emits the isReadyToSendWriteWithoutResponse value of a peripheral. - public var isReadyToSendWriteWithoutResponseChannel: AnyPublisher { - peripheralDelegate.isReadyToSendWriteWithoutResponseSubject - .first() - .eraseToAnyPublisher() - } + /// A publisher that emits the discovered services of the peripheral. + public var discoveredServicesChannel: AnyPublisher<[CBService]?, Error> { + peripheralDelegate.discoveredServicesSubject + .tryMap { result in + if let e = result.error { + throw e + } else { + return result.value + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the discovered characteristics of a service. + public var discoveredCharacteristicsChannel: + AnyPublisher<(CBService, [CBCharacteristic]?)?, Error> + { + peripheralDelegate.discoveredCharacteristicsSubject + .tryMap { result in + if let e = result.error { + throw e + } else { + return result.value + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the discovered descriptors of a characteristic. + public var discoveredDescriptorsChannel: + AnyPublisher<(CBCharacteristic, [CBDescriptor]?)?, Error> + { + peripheralDelegate.discoveredDescriptorsSubject + .tryMap { result in + if let e = result.error { + throw e + } else { + return result.value + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the updated value of a characteristic. + public var updatedCharacteristicValuesChannel: + AnyPublisher<(CBCharacteristic, Error?), Never> + { + peripheralDelegate.updatedCharacteristicValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the updated value of a descriptor. + public var updatedDescriptorValuesChannel: AnyPublisher<(CBDescriptor, Error?), Never> { + peripheralDelegate.updatedDescriptorValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the written value of a characteristic. + public var writtenCharacteristicValuesChannel: + AnyPublisher<(CBCharacteristic, Error?), Never> + { + peripheralDelegate.writtenCharacteristicValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the written value of a descriptor. + public var writtenDescriptorValuesChannel: AnyPublisher<(CBDescriptor, Error?), Never> { + peripheralDelegate.writtenDescriptorValuesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the notification state of a characteristic. + public var notificationStateChannel: AnyPublisher<(CBCharacteristic, Error?), Never> { + peripheralDelegate.notificationStateSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the update name of a peripheral. + public var updateNameChannel: AnyPublisher { + peripheralDelegate.updateNameSubject + .eraseToAnyPublisher() + } + + public var modifyServices: AnyPublisher<[CBService], Never> { + peripheralDelegate.modifyServicesSubject + .eraseToAnyPublisher() + } + + /// A publisher that emits the read RSSI value of a peripheral. + public var readRSSIChannel: AnyPublisher { + peripheralDelegate.readRSSISubject + .tryMap { rssi in + if let error = rssi.1 { + throw error + } else { + return rssi.0 + } + } + .eraseToAnyPublisher() + } + + /// A publisher that emits the isReadyToSendWriteWithoutResponse value of a peripheral. + public var isReadyToSendWriteWithoutResponseChannel: AnyPublisher { + peripheralDelegate.isReadyToSendWriteWithoutResponseSubject + .first() + .eraseToAnyPublisher() + } } diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift index bae33bd..f8babe3 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift @@ -10,84 +10,84 @@ import CoreBluetoothMock import Foundation struct BluetoothOperationResult { - let value: T - let error: Error? - let id: UUID + let value: T + let error: Error? + let id: UUID } struct IdentifiableOperation { - let id: UUID - let block: () -> Void + let id: UUID + let block: () -> Void } class SingleTaskQueue { - private var queue = Queue() - let l = L(category: "SingleTaskQueue") - private let accessQueue = DispatchQueue(label: "com.ble-library.SingleTaskQueue") - - func addOperation(_ task: IdentifiableOperation) { - accessQueue.sync { - l.i("add operation \(task.id)") - if queue.isEmpty { - l.i("queue is empty") - queue.enqueue(task) - task.block() - } else { - l.i("some tasks") - queue.enqueue(task) - } - } - } - - func dequeue() -> IdentifiableOperation? { - var task: IdentifiableOperation? - accessQueue.sync { - task = queue.dequeue() - } - l.i("dequeue: \(task?.id.uuidString ?? "no task")") - return task - } - - func runNext() { - accessQueue.sync { - let task = queue.peek() - l.i("run next: \(task?.id.uuidString ?? "no task")") - task?.block() - } - } + private var queue = Queue() + let l = L(category: "SingleTaskQueue") + private let accessQueue = DispatchQueue(label: "com.ble-library.SingleTaskQueue") + + func addOperation(_ task: IdentifiableOperation) { + accessQueue.sync { + l.i("add operation \(task.id)") + if queue.isEmpty { + l.i("queue is empty") + queue.enqueue(task) + task.block() + } else { + l.i("some tasks") + queue.enqueue(task) + } + } + } + + func dequeue() -> IdentifiableOperation? { + var task: IdentifiableOperation? + accessQueue.sync { + task = queue.dequeue() + } + l.i("dequeue: \(task?.id.uuidString ?? "no task")") + return task + } + + func runNext() { + accessQueue.sync { + let task = queue.peek() + l.i("run next: \(task?.id.uuidString ?? "no task")") + task?.block() + } + } } open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { let l = L(category: #file) - - typealias NonFailureSubject = PassthroughSubject - - struct TaskID { - let id: UUID - let task: () -> () - } - - var discoveredServicesQueue = SingleTaskQueue() - var discoveredCharacteristicsQueue = Queue() - var discoveredDescriptorsQueue = Queue() - - // MARK: Discovering Services + + typealias NonFailureSubject = PassthroughSubject + + struct TaskID { + let id: UUID + let task: () -> Void + } + + var discoveredServicesQueue = SingleTaskQueue() + var discoveredCharacteristicsQueue = Queue() + var discoveredDescriptorsQueue = Queue() + + // MARK: Discovering Services let discoveredServicesSubject = NonFailureSubject< - BluetoothOperationResult<[CBService]?> - >() - - /* + BluetoothOperationResult<[CBService]?> + >() + + /* let discoveredIncludedServicesSubject = PassthroughSubject< BluetoothOperationResult<(CBService, [CBService]?)>, Never >() */ - - // MARK: Discovering Characteristics and their Descriptors + + // MARK: Discovering Characteristics and their Descriptors let discoveredCharacteristicsSubject = NonFailureSubject< - BluetoothOperationResult<(CBService, [CBCharacteristic]?)> + BluetoothOperationResult<(CBService, [CBCharacteristic]?)> >() let discoveredDescriptorsSubject = NonFailureSubject< - BluetoothOperationResult<(CBCharacteristic, [CBDescriptor]?)> + BluetoothOperationResult<(CBCharacteristic, [CBDescriptor]?)> >() // MARK: Retrieving Characteristic and Descriptor Values @@ -97,8 +97,8 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { let updatedDescriptorValuesSubject = PassthroughSubject< (CBDescriptor, Error?), Never >() - - let isReadyToSendWriteWithoutResponseSubject = PassthroughSubject() + + let isReadyToSendWriteWithoutResponseSubject = PassthroughSubject() let writtenCharacteristicValuesSubject = PassthroughSubject< (CBCharacteristic, Error?), Never @@ -114,25 +114,25 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { // MARK: Monitoring Changes to a Peripheral’s Name or Services let updateNameSubject = PassthroughSubject() - let modifyServicesSubject = PassthroughSubject<[CBService], Never>() - - let readRSSISubject = PassthroughSubject<(NSNumber, Error?), Never>() - - // MARK: - Channels - - + let modifyServicesSubject = PassthroughSubject<[CBService], Never>() + + let readRSSISubject = PassthroughSubject<(NSNumber, Error?), Never>() + + // MARK: - Channels + // MARK: Discovering Services open func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) { - let operation = discoveredServicesQueue.dequeue()! - - let result = BluetoothOperationResult<[CBService]?>(value: peripheral.services, error: error, id: operation.id) - - discoveredServicesSubject.send(result) - discoveredServicesQueue.runNext() + let operation = discoveredServicesQueue.dequeue()! + + let result = BluetoothOperationResult<[CBService]?>( + value: peripheral.services, error: error, id: operation.id) + + discoveredServicesSubject.send(result) + discoveredServicesQueue.runNext() } - /* + /* public func peripheral( _ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: Error? @@ -146,10 +146,11 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { open func peripheral( _ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error? - ) { - let operationId = discoveredCharacteristicsQueue.dequeue()! - let result = BluetoothOperationResult<(CBService, [CBCharacteristic]?)>(value: (service, service.characteristics), error: error, id: operationId) - + ) { + let operationId = discoveredCharacteristicsQueue.dequeue()! + let result = BluetoothOperationResult<(CBService, [CBCharacteristic]?)>( + value: (service, service.characteristics), error: error, id: operationId) + discoveredCharacteristicsSubject.send(result) } @@ -157,9 +158,11 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { _ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error? ) { - let operationId = discoveredDescriptorsQueue.dequeue()! - let result = BluetoothOperationResult<(CBCharacteristic, [CBDescriptor]?)>(value: (characteristic, characteristic.descriptors), error: error, id: operationId) - + let operationId = discoveredDescriptorsQueue.dequeue()! + let result = BluetoothOperationResult<(CBCharacteristic, [CBDescriptor]?)>( + value: (characteristic, characteristic.descriptors), error: error, + id: operationId) + discoveredDescriptorsSubject.send(result) } @@ -195,8 +198,8 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { } open func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) { - isReadyToSendWriteWithoutResponseSubject.send(()) - } + isReadyToSendWriteWithoutResponseSubject.send(()) + } // MARK: Managing Notifications for a Characteristic’s Value @@ -212,7 +215,7 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { open func peripheral( _ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error? ) { - readRSSISubject.send((RSSI, error)) + readRSSISubject.send((RSSI, error)) } // MARK: Monitoring Changes to a Peripheral’s Name or Services @@ -224,11 +227,11 @@ open class ReactivePeripheralDelegate: NSObject, CBPeripheralDelegate { open func peripheral( _ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService] ) { - modifyServicesSubject.send(invalidatedServices) + modifyServicesSubject.send(invalidatedServices) } // MARK: Monitoring L2CAP Channels -/* + /* public func peripheral( _ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error? ) { diff --git a/Sources/iOS-BLE-Library-Mock/Utilities/Logger.swift b/Sources/iOS-BLE-Library-Mock/Utilities/Logger.swift index bf397ec..f1c7036 100644 --- a/Sources/iOS-BLE-Library-Mock/Utilities/Logger.swift +++ b/Sources/iOS-BLE-Library-Mock/Utilities/Logger.swift @@ -10,76 +10,76 @@ import os @available(iOS 14.0, macOS 11, *) private struct Loggers { - static var loggers: [UUID : Logger] = [:] + static var loggers: [UUID: Logger] = [:] } struct L { - @inline(__always) - static let enabled: Bool = false - - let subsystem: String - let category: String - - private let shouldLog: Bool - - private let id = UUID() - - init( - subsystem: String = "com.nordicsemi.ios_ble_library", category: String, - enabled: Bool = Self.enabled - ) { - self.subsystem = subsystem - self.category = category - self.shouldLog = enabled - - if #available(iOS 14, macOS 11, *) { - Loggers.loggers[self.id] = Logger(subsystem: subsystem, category: category) - } - } - - func i(_ msg: String) { -#if DEBUG - if !shouldLog { return } - - if #available(iOS 14, macOS 11, *) { - Loggers.loggers[id]?.info("\(msg)") - } else { - os_log("%@", type: .info, msg) - } - -#endif - } - - func d(_ msg: String) { -#if DEBUG - if !shouldLog { return } - if #available(iOS 14, macOS 11, *) { - Loggers.loggers[id]?.debug("\(msg)") - } else { - os_log("%@", type: .debug, msg) - } -#endif - } - - func e(_ msg: String) { -#if DEBUG - if !shouldLog { return } - if #available(iOS 14, macOS 11, *) { - Loggers.loggers[id]?.error("\(msg)") - } else { - os_log("%@", type: .error, msg) - } -#endif - } - - func f(_ msg: String) { -#if DEBUG - if !shouldLog { return } - if #available(iOS 14, macOS 11, *) { - Loggers.loggers[id]?.fault("\(msg)") - } else { - os_log("%@", type: .fault, msg) - } -#endif - } + @inline(__always) + static let enabled: Bool = false + + let subsystem: String + let category: String + + private let shouldLog: Bool + + private let id = UUID() + + init( + subsystem: String = "com.nordicsemi.ios_ble_library", category: String, + enabled: Bool = Self.enabled + ) { + self.subsystem = subsystem + self.category = category + self.shouldLog = enabled + + if #available(iOS 14, macOS 11, *) { + Loggers.loggers[self.id] = Logger(subsystem: subsystem, category: category) + } + } + + func i(_ msg: String) { + #if DEBUG + if !shouldLog { return } + + if #available(iOS 14, macOS 11, *) { + Loggers.loggers[id]?.info("\(msg)") + } else { + os_log("%@", type: .info, msg) + } + + #endif + } + + func d(_ msg: String) { + #if DEBUG + if !shouldLog { return } + if #available(iOS 14, macOS 11, *) { + Loggers.loggers[id]?.debug("\(msg)") + } else { + os_log("%@", type: .debug, msg) + } + #endif + } + + func e(_ msg: String) { + #if DEBUG + if !shouldLog { return } + if #available(iOS 14, macOS 11, *) { + Loggers.loggers[id]?.error("\(msg)") + } else { + os_log("%@", type: .error, msg) + } + #endif + } + + func f(_ msg: String) { + #if DEBUG + if !shouldLog { return } + if #available(iOS 14, macOS 11, *) { + Loggers.loggers[id]?.fault("\(msg)") + } else { + os_log("%@", type: .fault, msg) + } + #endif + } } diff --git a/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift b/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift index bb41d71..6c86d86 100644 --- a/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift +++ b/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift @@ -17,16 +17,16 @@ extension Publisher { } extension Publishers { - - /** + + /** A publisher that is used for most of the Bluetooth operations. - + # Overview This publisher conforms to the `ConnectablePublisher` protocol because most of the Bluetooth operations have to be set up before they can be used. - + It means that the publisher will not emit any values until it is connected. The connection is established by calling the `connect()` or `autoconnect()` methods. To learn more about the `ConnectablePublisher` protocol, see [Apple's documentation](https://developer.apple.com/documentation/combine/connectablepublisher). - + ```swift let publisher = centralManager.scanForPeripherals(withServices: nil) .autoconnect() diff --git a/Sources/iOS-BLE-Library-Mock/Utilities/Queue.swift b/Sources/iOS-BLE-Library-Mock/Utilities/Queue.swift index c9c5f74..e514773 100644 --- a/Sources/iOS-BLE-Library-Mock/Utilities/Queue.swift +++ b/Sources/iOS-BLE-Library-Mock/Utilities/Queue.swift @@ -1,6 +1,6 @@ // // File.swift -// +// // // Created by Nick Kibysh on 03/11/2023. // @@ -8,58 +8,58 @@ import Foundation class Node { - var value: T - var next: Node? + var value: T + var next: Node? - init(value: T) { - self.value = value - } + init(value: T) { + self.value = value + } } class Queue { - private var front: Node? - private var rear: Node? - private let accessQueue = DispatchQueue(label: "com.ble-library.threadSafeQueue") + private var front: Node? + private var rear: Node? + private let accessQueue = DispatchQueue(label: "com.ble-library.threadSafeQueue") - var isEmpty: Bool { - return front == nil - } + var isEmpty: Bool { + return front == nil + } - // Enqueue operation to add an element to the rear of the queue - func enqueue(_ value: T) { - accessQueue.sync { - let newNode = Node(value: value) - if isEmpty { - front = newNode - rear = newNode - } else { - rear?.next = newNode - rear = newNode - } - } - } + // Enqueue operation to add an element to the rear of the queue + func enqueue(_ value: T) { + accessQueue.sync { + let newNode = Node(value: value) + if isEmpty { + front = newNode + rear = newNode + } else { + rear?.next = newNode + rear = newNode + } + } + } - // Dequeue operation to remove and return the element from the front of the queue - func dequeue() -> T? { - var element: T? - accessQueue.sync { - if let currentFront = front { - front = currentFront.next - if front == nil { - rear = nil - } - element = currentFront.value - } else { - element = nil - } - } - return element - } + // Dequeue operation to remove and return the element from the front of the queue + func dequeue() -> T? { + var element: T? + accessQueue.sync { + if let currentFront = front { + front = currentFront.next + if front == nil { + rear = nil + } + element = currentFront.value + } else { + element = nil + } + } + return element + } - // Peek operation to get the value at the front of the queue without removing it - func peek() -> T? { - return front?.value - } + // Peek operation to get the value at the front of the queue without removing it + func peek() -> T? { + return front?.value + } } /* struct Queue { @@ -81,7 +81,7 @@ struct Queue { } return element } - + var head: T? { var element: T? accessQueue.sync {