From 7d649c606c1037efd6a84edb63c4fa46744bbe14 Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Thu, 5 Oct 2023 11:55:04 +0200 Subject: [PATCH 1/4] replaced BluetoothPublisher with AnyPublisher --- .../xcschemes/iOS-BLE-Library-Mock.xcscheme | 66 +++++++++++++++++++ .../xcschemes/iOS-BLE-LibraryTests.xcscheme | 53 +++++++++++++++ .../CentralManager/CentralManager.swift | 12 +++- .../Peripheral/Peripheral.swift | 57 ++++++---------- .../ReactivePeripheralDelegate.swift | 18 +++-- 5 files changed, 155 insertions(+), 51 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-Library-Mock.xcscheme create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-LibraryTests.xcscheme diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-Library-Mock.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-Library-Mock.xcscheme new file mode 100644 index 0000000..98b5a44 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-Library-Mock.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-LibraryTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-LibraryTests.xcscheme new file mode 100644 index 0000000..300adb0 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/iOS-BLE-LibraryTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Sources/iOS-BLE-Library/CentralManager/CentralManager.swift b/Sources/iOS-BLE-Library/CentralManager/CentralManager.swift index a33ada7..dfc0379 100644 --- a/Sources/iOS-BLE-Library/CentralManager/CentralManager.swift +++ b/Sources/iOS-BLE-Library/CentralManager/CentralManager.swift @@ -123,7 +123,7 @@ extension CentralManager { /// If the connection was unsuccessful or disconnection returns an error (e.g., peripheral disconnected unexpectedly), /// the publisher finishes with an error. public func connect(_ peripheral: CBPeripheral, options: [String: Any]? = nil) - -> Publishers.BluetoothPublisher + -> AnyPublisher { let killSwitch = self.disconnectedPeripheralsChannel.tryFirst(where: { p in if let e = p.1 { @@ -145,12 +145,14 @@ extension CentralManager { .bluetooth { self.centralManager.connect(peripheral, options: options) } + .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) -> Publishers.BluetoothPublisher + public func cancelPeripheralConnection(_ peripheral: CBPeripheral) -> AnyPublisher { return self.disconnectedPeripheralsChannel .tryFilter { r in @@ -169,6 +171,8 @@ extension CentralManager { .bluetooth { self.centralManager.cancelPeripheralConnection(peripheral) } + .autoconnect() + .eraseToAnyPublisher() } } @@ -213,7 +217,7 @@ extension CentralManager { /// - Parameter services: The services to scan for. /// - Returns: A publisher that emits scan results or an error. public func scanForPeripherals(withServices services: [CBUUID]?) - -> Publishers.BluetoothPublisher + -> AnyPublisher { stopScan() return centralManagerDelegate.stateSubject @@ -239,6 +243,8 @@ extension CentralManager { .bluetooth { self.centralManager.scanForPeripherals(withServices: services) } + .autoconnect() + .eraseToAnyPublisher() } /// Stops an ongoing scan for peripherals. diff --git a/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift index 4582fb3..a216c26 100644 --- a/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift +++ b/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift @@ -77,6 +77,7 @@ private class NativeObserver: Observer { */ //CG_END + public class Peripheral { /// I'm Errr from Omicron Persei 8 public enum Err: Error { @@ -140,13 +141,12 @@ extension Peripheral { } extension Peripheral { - // TODO: Extract repeated code /// 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]?) - -> Publishers.BluetoothPublisher + -> AnyPublisher<[CBService], Error> { let allServices = peripheralDelegate.discoveredServicesSubject .tryCompactMap { result throws -> [CBService]? in @@ -155,23 +155,14 @@ extension Peripheral { } else { return result.0 } - } - .flatMap { services -> Publishers.Sequence<[CBService], Error> in - Publishers.Sequence(sequence: services) - } - - let filtered: AnyPublisher - - if let serviceList = serviceUUIDs { - filtered = allServices.guestList(serviceList, keypath: \.uuid) - .eraseToAnyPublisher() - } else { - filtered = allServices.eraseToAnyPublisher() - } + } + .first() - return filtered.bluetooth { + return allServices.bluetooth { self.peripheral.discoverServices(serviceUUIDs) } + .autoconnect() + .eraseToAnyPublisher() } /// Discover characteristics for a given service. @@ -182,7 +173,7 @@ extension Peripheral { /// - Returns: A publisher emitting discovered characteristics or an error. public func discoverCharacteristics( _ characteristicUUIDs: [CBUUID]?, for service: CBService - ) -> Publishers.BluetoothPublisher { + ) -> AnyPublisher<[CBCharacteristic], Error> { let allCharacteristics = peripheralDelegate.discoveredCharacteristicsSubject .filter { $0.0.uuid == service.uuid @@ -194,25 +185,13 @@ extension Peripheral { return result.1 } } - .flatMap { - characteristics -> Publishers.Sequence<[CBCharacteristic], Error> in - Publishers.Sequence(sequence: characteristics) - } - - let filtered: AnyPublisher + .first() - if let list = characteristicUUIDs { - filtered = - allCharacteristics - .guestList(list, keypath: \.uuid) - .eraseToAnyPublisher() - } else { - filtered = allCharacteristics.eraseToAnyPublisher() - } - - return filtered.bluetooth { + return allCharacteristics.bluetooth { self.peripheral.discoverCharacteristics(characteristicUUIDs, for: service) } + .autoconnect() + .eraseToAnyPublisher() } /// Discover descriptors for a given characteristic. @@ -220,7 +199,7 @@ extension Peripheral { /// - Parameter characteristic: The characteristic for which to discover descriptors. /// - Returns: A publisher emitting discovered descriptors or an error. public func discoverDescriptors(for characteristic: CBCharacteristic) - -> Publishers.BluetoothPublisher + -> AnyPublisher<[CBDescriptor], Error> { return peripheralDelegate.discoveredDescriptorsSubject .filter { @@ -233,12 +212,12 @@ extension Peripheral { return result.1 } } - .flatMap { descriptors -> Publishers.Sequence<[CBDescriptor], Error> in - Publishers.Sequence(sequence: descriptors) - } + .first() .bluetooth { self.peripheral.discoverDescriptors(for: characteristic) } + .autoconnect() + .eraseToAnyPublisher() } } @@ -251,7 +230,7 @@ extension Peripheral { /// - characteristic: The characteristic to write to. /// - Returns: A publisher indicating success or an error. public func writeValueWithResponse(_ data: Data, for characteristic: CBCharacteristic) - -> Publishers.BluetoothPublisher + -> AnyPublisher { return peripheralDelegate.writtenCharacteristicValuesSubject .first(where: { $0.0.uuid == characteristic.uuid }) @@ -266,6 +245,8 @@ extension Peripheral { self.peripheral.writeValue( data, for: characteristic, type: .withResponse) } + .autoconnect() + .eraseToAnyPublisher() } /// Write data to a characteristic without waiting for a response. diff --git a/Sources/iOS-BLE-Library/Peripheral/ReactivePeripheralDelegate.swift b/Sources/iOS-BLE-Library/Peripheral/ReactivePeripheralDelegate.swift index 5dc9de8..238f45c 100644 --- a/Sources/iOS-BLE-Library/Peripheral/ReactivePeripheralDelegate.swift +++ b/Sources/iOS-BLE-Library/Peripheral/ReactivePeripheralDelegate.swift @@ -17,12 +17,14 @@ import Foundation public class ReactivePeripheralDelegate: NSObject { let l = L(category: #file) - - // MARK: Subjects + + // MARK: Discovering Services public let discoveredServicesSubject = PassthroughSubject<([CBService]?, Error?), Never>() public let discoveredIncludedServicesSubject = PassthroughSubject< (CBService, [CBService]?, Error?), Never >() + + // MARK: Discovering Characteristics and their Descriptors public let discoveredCharacteristicsSubject = PassthroughSubject< (CBService, [CBCharacteristic]?, Error?), Never >() @@ -52,6 +54,7 @@ public class ReactivePeripheralDelegate: NSObject { // MARK: Monitoring Changes to a Peripheral’s Name or Services public let updateNameSubject = PassthroughSubject() + public let modifyServicesSubject = PassthroughSubject<[CBService], Never>() } extension ReactivePeripheralDelegate: CBPeripheralDelegate { @@ -148,11 +151,6 @@ extension ReactivePeripheralDelegate: CBPeripheralDelegate { fatalError() } - public func peripheralDidUpdateRSSI(_ peripheral: CBPeripheral, error: Error?) { - l.i(#function) - fatalError() - } - // MARK: Monitoring Changes to a Peripheral’s Name or Services public func peripheralDidUpdateName(_ peripheral: CBPeripheral) { @@ -164,16 +162,16 @@ extension ReactivePeripheralDelegate: CBPeripheralDelegate { _ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService] ) { l.i(#function) - fatalError() + modifyServicesSubject.send(invalidatedServices) } // MARK: Monitoring L2CAP Channels - +/* public func peripheral( _ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error? ) { l.i(#function) fatalError() } - +*/ } From 1b4059d0ec0f8e80b80cdcc0a7b69e2ab246b2a9 Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Fri, 6 Oct 2023 10:27:52 +0200 Subject: [PATCH 2/4] applied changes on *-mock version --- .github/workflows/release.yml | 1 + Sources/iOS-BLE-Library-Mock/Alias.swift | 87 ++++---- .../CentralManager/CentralManager.swift | 38 ++-- .../Peripheral/Peripheral.swift | 196 ++++++++---------- .../ReactivePeripheralDelegate.swift | 18 +- .../Publishers/Publishers+Bluetooth.swift | 10 +- .../CentralManagerTests.swift | 9 - 7 files changed, 160 insertions(+), 199 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21dd282..2a5bfa1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -42,6 +42,7 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v4 + - name: Run copy script run: | python3 code_gen/code_gen.py Sources diff --git a/Sources/iOS-BLE-Library-Mock/Alias.swift b/Sources/iOS-BLE-Library-Mock/Alias.swift index 3f8811d..b04c7c5 100644 --- a/Sources/iOS-BLE-Library-Mock/Alias.swift +++ b/Sources/iOS-BLE-Library-Mock/Alias.swift @@ -38,59 +38,50 @@ 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 +public let CBConnectPeripheralOptionNotifyOnConnectionKey = CBMConnectPeripheralOptionNotifyOnConnectionKey +public let CBConnectPeripheralOptionNotifyOnDisconnectionKey = CBMConnectPeripheralOptionNotifyOnDisconnectionKey +public let CBConnectPeripheralOptionNotifyOnNotificationKey = CBMConnectPeripheralOptionNotifyOnNotificationKey \ No newline at end of file diff --git a/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift b/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift index 016756c..23f3e1a 100644 --- a/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift +++ b/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift @@ -18,8 +18,7 @@ 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: @@ -55,7 +54,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) @@ -64,7 +63,7 @@ public class CentralManager { /// The underlying CBCentralManager instance. public let centralManager: CBCentralManager - + /// The reactive delegate for the ``centralManager``. public let centralManagerDelegate: ReactiveCentralManagerDelegate @@ -74,12 +73,10 @@ 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() } @@ -113,7 +110,7 @@ extension CentralManager { /// If the connection was unsuccessful or disconnection returns an error (e.g., peripheral disconnected unexpectedly), /// the publisher finishes with an error. public func connect(_ peripheral: CBPeripheral, options: [String: Any]? = nil) - -> Publishers.BluetoothPublisher + -> AnyPublisher { let killSwitch = self.disconnectedPeripheralsChannel.tryFirst(where: { p in if let e = p.1 { @@ -135,13 +132,14 @@ extension CentralManager { .bluetooth { self.centralManager.connect(peripheral, options: options) } + .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) - -> Publishers.BluetoothPublisher + public func cancelPeripheralConnection(_ peripheral: CBPeripheral) -> AnyPublisher { return self.disconnectedPeripheralsChannel .tryFilter { r in @@ -157,15 +155,17 @@ extension CentralManager { } .map { $0.0 } .first() - .bluetooth { - self.centralManager.cancelPeripheralConnection(peripheral) - } + .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. /// @@ -198,13 +198,13 @@ 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]?) - -> Publishers.BluetoothPublisher + -> AnyPublisher { stopScan() return centralManagerDelegate.stateSubject @@ -230,6 +230,8 @@ extension CentralManager { .bluetooth { self.centralManager.scanForPeripherals(withServices: services) } + .autoconnect() + .eraseToAnyPublisher() } /// Stops an ongoing scan for peripherals. diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift index ca57ff6..fa16933 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift @@ -7,6 +7,7 @@ import Combine import CoreBluetooth + import CoreBluetoothMock import Foundation @@ -42,31 +43,28 @@ private class NativeObserver: Observer { } private class MockObserver: Observer { - @objc private var peripheral: CBMPeripheralMock + @objc private var peripheral: CBMPeripheralMock - private weak var publisher: CurrentValueSubject! - private var observation: NSKeyValueObservation? + private weak var publisher: CurrentValueSubject! + private var observation: NSKeyValueObservation? - init( - peripheral: CBMPeripheralMock, - publisher: CurrentValueSubject - ) { - self.peripheral = peripheral - self.publisher = publisher - super.init() - } + 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) + } + } + } + } - 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 { /// I'm Errr from Omicron Persei 8 @@ -96,22 +94,22 @@ public class 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. + /// + /// - Parameters: + /// - peripheral: The CBPeripheral to manage. + /// - delegate: The delegate for handling peripheral events. public init(peripheral: CBPeripheral, delegate: ReactivePeripheralDelegate) { self.peripheral = peripheral self.peripheralDelegate = delegate 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() + } } } @@ -124,13 +122,12 @@ extension Peripheral { } extension Peripheral { - // TODO: Extract repeated code /// 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. + /// + /// - 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]?) - -> Publishers.BluetoothPublisher + -> AnyPublisher<[CBService], Error> { let allServices = peripheralDelegate.discoveredServicesSubject .tryCompactMap { result throws -> [CBService]? in @@ -139,34 +136,25 @@ extension Peripheral { } else { return result.0 } - } - .flatMap { services -> Publishers.Sequence<[CBService], Error> in - Publishers.Sequence(sequence: services) - } + } + .first() - let filtered: AnyPublisher - - if let serviceList = serviceUUIDs { - filtered = allServices.guestList(serviceList, keypath: \.uuid) - .eraseToAnyPublisher() - } else { - filtered = allServices.eraseToAnyPublisher() - } - - return filtered.bluetooth { + return allServices.bluetooth { self.peripheral.discoverServices(serviceUUIDs) } + .autoconnect() + .eraseToAnyPublisher() } /// 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 - ) -> Publishers.BluetoothPublisher { + ) -> AnyPublisher<[CBCharacteristic], Error> { let allCharacteristics = peripheralDelegate.discoveredCharacteristicsSubject .filter { $0.0.uuid == service.uuid @@ -178,33 +166,21 @@ extension Peripheral { return result.1 } } - .flatMap { - characteristics -> Publishers.Sequence<[CBCharacteristic], Error> in - Publishers.Sequence(sequence: characteristics) - } + .first() - let filtered: AnyPublisher - - if let list = characteristicUUIDs { - filtered = - allCharacteristics - .guestList(list, keypath: \.uuid) - .eraseToAnyPublisher() - } else { - filtered = allCharacteristics.eraseToAnyPublisher() - } - - return filtered.bluetooth { + return allCharacteristics.bluetooth { self.peripheral.discoverCharacteristics(characteristicUUIDs, for: service) } + .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) - -> Publishers.BluetoothPublisher + -> AnyPublisher<[CBDescriptor], Error> { return peripheralDelegate.discoveredDescriptorsSubject .filter { @@ -217,25 +193,25 @@ extension Peripheral { return result.1 } } - .flatMap { descriptors -> Publishers.Sequence<[CBDescriptor], Error> in - Publishers.Sequence(sequence: descriptors) - } + .first() .bluetooth { self.peripheral.discoverDescriptors(for: characteristic) } + .autoconnect() + .eraseToAnyPublisher() } } // 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) - -> Publishers.BluetoothPublisher + -> AnyPublisher { return peripheralDelegate.writtenCharacteristicValuesSubject .first(where: { $0.0.uuid == characteristic.uuid }) @@ -250,22 +226,24 @@ extension Peripheral { self.peripheral.writeValue( data, for: characteristic, type: .withResponse) } + .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) { fatalError() } @@ -274,17 +252,17 @@ extension Peripheral { // 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. + /// + /// - 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 reader.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. + /// + /// - 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 @@ -300,9 +278,9 @@ extension Peripheral { } /// Read the value of a descriptor. - /// - /// - Parameter descriptor: The descriptor to read from. - /// - Returns: A future emitting the read data or an error. + /// + /// - Parameter descriptor: The descriptor to read from. + /// - Returns: A future emitting the read data or an error. public func readValue(for descriptor: CBDescriptor) -> Future { fatalError() } @@ -311,11 +289,11 @@ extension Peripheral { // 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) -> Publishers.BluetoothPublisher { diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift index c342031..725d6d0 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/ReactivePeripheralDelegate.swift @@ -11,12 +11,14 @@ import Foundation public class ReactivePeripheralDelegate: NSObject { let l = L(category: #file) - - // MARK: Subjects + + // MARK: Discovering Services public let discoveredServicesSubject = PassthroughSubject<([CBService]?, Error?), Never>() public let discoveredIncludedServicesSubject = PassthroughSubject< (CBService, [CBService]?, Error?), Never >() + + // MARK: Discovering Characteristics and their Descriptors public let discoveredCharacteristicsSubject = PassthroughSubject< (CBService, [CBCharacteristic]?, Error?), Never >() @@ -46,6 +48,7 @@ public class ReactivePeripheralDelegate: NSObject { // MARK: Monitoring Changes to a Peripheral’s Name or Services public let updateNameSubject = PassthroughSubject() + public let modifyServicesSubject = PassthroughSubject<[CBService], Never>() } extension ReactivePeripheralDelegate: CBPeripheralDelegate { @@ -142,11 +145,6 @@ extension ReactivePeripheralDelegate: CBPeripheralDelegate { fatalError() } - public func peripheralDidUpdateRSSI(_ peripheral: CBPeripheral, error: Error?) { - l.i(#function) - fatalError() - } - // MARK: Monitoring Changes to a Peripheral’s Name or Services public func peripheralDidUpdateName(_ peripheral: CBPeripheral) { @@ -158,16 +156,16 @@ extension ReactivePeripheralDelegate: CBPeripheralDelegate { _ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService] ) { l.i(#function) - fatalError() + modifyServicesSubject.send(invalidatedServices) } // MARK: Monitoring L2CAP Channels - +/* public func peripheral( _ peripheral: CBPeripheral, didOpen channel: CBL2CAPChannel?, error: Error? ) { l.i(#function) fatalError() } - +*/ } 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 c979d0d..426e26f 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/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift b/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift index f9101b0..437601a 100644 --- a/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift +++ b/Tests/iOS-BLE-LibraryTests/CentralManagerTests.swift @@ -55,7 +55,6 @@ final class CentralManagerTests: XCTestCase { let completionExpectation = XCTestExpectation(description: "Publisher finished") central.scanForPeripherals(withServices: nil) - .autoconnect() .prefix(1) .sink(receiveCompletion: { completion in switch completion { @@ -78,7 +77,6 @@ final class CentralManagerTests: XCTestCase { let expectation = XCTestExpectation(description: "Scan for peripherals") central.scanForPeripherals(withServices: nil) - .autoconnect() .sink(receiveCompletion: { completion in switch completion { case .finished: @@ -107,7 +105,6 @@ final class CentralManagerTests: XCTestCase { let valueExpectation1 = XCTestExpectation(description: "1: Receive at least 1 value (ScanResult)") central.scanForPeripherals(withServices: nil) - .autoconnect() .sink(receiveCompletion: { completion in switch completion { case .finished: @@ -128,7 +125,6 @@ final class CentralManagerTests: XCTestCase { let secondExp = XCTestExpectation(description: "Repeated scan for peripherals") central.scanForPeripherals(withServices: nil) - .autoconnect() .sink(receiveCompletion: { completion in switch completion { case .finished: @@ -148,14 +144,12 @@ final class CentralManagerTests: XCTestCase { func testConnect() async throws { let connectionPeripheral = try await central.scanForPeripherals(withServices: nil) - .autoconnect() .value .peripheral let connectionExpectation = XCTestExpectation(description: "Connection expectation") let disconnectionExpectation = XCTestExpectation(description: "Disconnection expectation") central.connect(connectionPeripheral) - .autoconnect() .sink { completion in switch completion { case .finished: @@ -172,7 +166,6 @@ final class CentralManagerTests: XCTestCase { await fulfillment(of: [connectionExpectation], timeout: 3) central.cancelPeripheralConnection(connectionPeripheral) - .autoconnect() .sink { completion in if case .failure(let e) = completion { XCTFail(e.localizedDescription) @@ -187,7 +180,6 @@ final class CentralManagerTests: XCTestCase { func testDisconnectFromPeripheral() async throws { let connectionPeripheral = try await central.scanForPeripherals(withServices: nil) - .autoconnect() .value .peripheral @@ -195,7 +187,6 @@ final class CentralManagerTests: XCTestCase { let disconnectionExpectation = XCTestExpectation(description: "Disconnection expectation") central.connect(connectionPeripheral) - .autoconnect() .sink { completion in switch completion { case .finished: From ad66f3442134bd36efe788d3346d3285e5f936ab Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Fri, 6 Oct 2023 10:39:38 +0200 Subject: [PATCH 3/4] Bluetooth Publisher is not public animore --- Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift | 6 ++++-- .../Utilities/Publishers/Publishers+Bluetooth.swift | 2 +- Sources/iOS-BLE-Library/Peripheral/Peripheral.swift | 6 ++++-- .../Utilities/Publishers/Publishers+Bluetooth.swift | 2 +- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift index fa16933..f1329e1 100644 --- a/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift +++ b/Sources/iOS-BLE-Library-Mock/Peripheral/Peripheral.swift @@ -295,12 +295,12 @@ extension Peripheral { /// - 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) - -> Publishers.BluetoothPublisher + -> AnyPublisher { if characteristic.isNotifying == isEnabled { return Just(isEnabled) .setFailureType(to: Error.self) - .bluetooth {} + .eraseToAnyPublisher() } return peripheralDelegate.notificationStateSubject @@ -314,5 +314,7 @@ extension Peripheral { .bluetooth { self.peripheral.setNotifyValue(isEnabled, for: characteristic) } + .autoconnect() + .eraseToAnyPublisher() } } 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 426e26f..bb41d71 100644 --- a/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift +++ b/Sources/iOS-BLE-Library-Mock/Utilities/Publishers/Publishers+Bluetooth.swift @@ -37,7 +37,7 @@ extension Publishers { .store(in: &cancellables) ``` */ - public class BluetoothPublisher: ConnectablePublisher { + class BluetoothPublisher: ConnectablePublisher { private let inner: BaseConnectable diff --git a/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift b/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift index a216c26..eb983c0 100644 --- a/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift +++ b/Sources/iOS-BLE-Library/Peripheral/Peripheral.swift @@ -314,12 +314,12 @@ extension Peripheral { /// - 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) - -> Publishers.BluetoothPublisher + -> AnyPublisher { if characteristic.isNotifying == isEnabled { return Just(isEnabled) .setFailureType(to: Error.self) - .bluetooth {} + .eraseToAnyPublisher() } return peripheralDelegate.notificationStateSubject @@ -333,5 +333,7 @@ extension Peripheral { .bluetooth { self.peripheral.setNotifyValue(isEnabled, for: characteristic) } + .autoconnect() + .eraseToAnyPublisher() } } diff --git a/Sources/iOS-BLE-Library/Utilities/Publishers/Publishers+Bluetooth.swift b/Sources/iOS-BLE-Library/Utilities/Publishers/Publishers+Bluetooth.swift index 426e26f..bb41d71 100644 --- a/Sources/iOS-BLE-Library/Utilities/Publishers/Publishers+Bluetooth.swift +++ b/Sources/iOS-BLE-Library/Utilities/Publishers/Publishers+Bluetooth.swift @@ -37,7 +37,7 @@ extension Publishers { .store(in: &cancellables) ``` */ - public class BluetoothPublisher: ConnectablePublisher { + class BluetoothPublisher: ConnectablePublisher { private let inner: BaseConnectable From 04ca5e69522edbe37178eeb712a7c549bf205cdf Mon Sep 17 00:00:00 2001 From: Nick Kibysh Date: Fri, 6 Oct 2023 11:44:17 +0200 Subject: [PATCH 4/4] updated documentation --- .../CentralManager/CentralManager.swift | 20 +++++++++++++ .../CentralManager/CentralManager.md | 29 ++----------------- .../CentralManager/CentralManager/connect.md | 7 +++++ .../CentralManager/CentralManager.swift | 20 +++++++++++++ .../CentralManager/CentralManager.md | 29 ++----------------- .../CentralManager/CentralManager/connect.md | 7 +++++ 6 files changed, 58 insertions(+), 54 deletions(-) create mode 100644 Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager/connect.md create mode 100644 Sources/iOS-BLE-Library/Documentation.docc/CentralManager/CentralManager/connect.md diff --git a/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift b/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift index 23f3e1a..87dd282 100644 --- a/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift +++ b/Sources/iOS-BLE-Library-Mock/CentralManager/CentralManager.swift @@ -109,6 +109,26 @@ 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: + /// print("Peripheral disconnected successfully") + /// case .failure(let error): + /// print("Error: \(error)") + /// } + /// } receiveValue: { peripheral in + /// print("Peripheral connected: \(peripheral)") + /// } + /// .store(in: &cancellables) + /// ``` public func connect(_ peripheral: CBPeripheral, options: [String: Any]? = nil) -> AnyPublisher { diff --git a/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager.md b/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager.md index 19e34cc..5a74a87 100644 --- a/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager.md +++ b/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager.md @@ -2,37 +2,12 @@ ### Create a Central Manager -Since it's not recommended to override the `CBCentralManager`'s methods, ``CentralManager`` is merely a wrapper around `CBCentralManager` with an instance of it inside. +``CentralManager`` is merely a wrapper around `CBCentralManager` with an instance of it inside. -The new instance of `CBCentralManager` can be created during initialization using ``init(centralManagerDelegate:queue:)``, or an existing instance can be passed using ``init(centralManager:)``. +The new instance of `CBCentralManager` can be created during initialization using ``init(centralManagerDelegate:queue:options:)``, or an existing instance can be passed using ``init(centralManager:)``. If you pass a central manager inside ``init(centralManager:)``, it should already have a delegate set. The delegate should be an instance of ``ReactiveCentralManagerDelegate``; otherwise, an error will be thrown. -### Connection - -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. - -> The publisher returned by ``CentralManager/connect(_:options:)`` is a `ConnectablePublisher`. Therefore, you need to call `connect()` or `autoconnect()` to initiate the connection process. - -```swift -centralManager.connect(peripheral) - .autoconnect() - .sink { completion in - switch completion { - case .finished: - print("Peripheral disconnected successfully") - case .failure(let error): - print("Error: \(error)") - } - } receiveValue: { peripheral in - print("Peripheral connected: \(peripheral)") - } - .store(in: &cancellables) -``` - ### Channels Channels are used to pass through data from the `CBCentralManagerDelegate` methods. diff --git a/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager/connect.md b/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager/connect.md new file mode 100644 index 0000000..fdaa466 --- /dev/null +++ b/Sources/iOS-BLE-Library-Mock/Documentation.docc/CentralManager/CentralManager/connect.md @@ -0,0 +1,7 @@ +# ``iOS_BLE_Library/CentralManager/connect(_:options:)`` + +## See Also + +- ``CentralManager/connectedPeripheralChannel`` +- ``CentralManager/disconnectedPeripheralsChannel`` + diff --git a/Sources/iOS-BLE-Library/CentralManager/CentralManager.swift b/Sources/iOS-BLE-Library/CentralManager/CentralManager.swift index dfc0379..98938de 100644 --- a/Sources/iOS-BLE-Library/CentralManager/CentralManager.swift +++ b/Sources/iOS-BLE-Library/CentralManager/CentralManager.swift @@ -122,6 +122,26 @@ 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: + /// print("Peripheral disconnected successfully") + /// case .failure(let error): + /// print("Error: \(error)") + /// } + /// } receiveValue: { peripheral in + /// print("Peripheral connected: \(peripheral)") + /// } + /// .store(in: &cancellables) + /// ``` public func connect(_ peripheral: CBPeripheral, options: [String: Any]? = nil) -> AnyPublisher { diff --git a/Sources/iOS-BLE-Library/Documentation.docc/CentralManager/CentralManager.md b/Sources/iOS-BLE-Library/Documentation.docc/CentralManager/CentralManager.md index 19e34cc..5a74a87 100644 --- a/Sources/iOS-BLE-Library/Documentation.docc/CentralManager/CentralManager.md +++ b/Sources/iOS-BLE-Library/Documentation.docc/CentralManager/CentralManager.md @@ -2,37 +2,12 @@ ### Create a Central Manager -Since it's not recommended to override the `CBCentralManager`'s methods, ``CentralManager`` is merely a wrapper around `CBCentralManager` with an instance of it inside. +``CentralManager`` is merely a wrapper around `CBCentralManager` with an instance of it inside. -The new instance of `CBCentralManager` can be created during initialization using ``init(centralManagerDelegate:queue:)``, or an existing instance can be passed using ``init(centralManager:)``. +The new instance of `CBCentralManager` can be created during initialization using ``init(centralManagerDelegate:queue:options:)``, or an existing instance can be passed using ``init(centralManager:)``. If you pass a central manager inside ``init(centralManager:)``, it should already have a delegate set. The delegate should be an instance of ``ReactiveCentralManagerDelegate``; otherwise, an error will be thrown. -### Connection - -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. - -> The publisher returned by ``CentralManager/connect(_:options:)`` is a `ConnectablePublisher`. Therefore, you need to call `connect()` or `autoconnect()` to initiate the connection process. - -```swift -centralManager.connect(peripheral) - .autoconnect() - .sink { completion in - switch completion { - case .finished: - print("Peripheral disconnected successfully") - case .failure(let error): - print("Error: \(error)") - } - } receiveValue: { peripheral in - print("Peripheral connected: \(peripheral)") - } - .store(in: &cancellables) -``` - ### Channels Channels are used to pass through data from the `CBCentralManagerDelegate` methods. diff --git a/Sources/iOS-BLE-Library/Documentation.docc/CentralManager/CentralManager/connect.md b/Sources/iOS-BLE-Library/Documentation.docc/CentralManager/CentralManager/connect.md new file mode 100644 index 0000000..fdaa466 --- /dev/null +++ b/Sources/iOS-BLE-Library/Documentation.docc/CentralManager/CentralManager/connect.md @@ -0,0 +1,7 @@ +# ``iOS_BLE_Library/CentralManager/connect(_:options:)`` + +## See Also + +- ``CentralManager/connectedPeripheralChannel`` +- ``CentralManager/disconnectedPeripheralsChannel`` +