diff --git a/Sources/BLECombineKit/Central/BLECentralManager.swift b/Sources/BLECombineKit/Central/BLECentralManager.swift index 6beb176..4c4bab2 100644 --- a/Sources/BLECombineKit/Central/BLECentralManager.swift +++ b/Sources/BLECombineKit/Central/BLECentralManager.swift @@ -19,16 +19,22 @@ public protocol BLECentralManager: AnyObject { var isScanning: Bool { get } /// The current state as a publisher. - var state: AnyPublisher { get } + var state: AnyPublisher { get } - /// Retrieve peripherals given a set of identifiers. - /// This method will generate events for any matching peripheral or an error. + /// Retrieve a list of known peripherals by their identifiers. + /// + /// The returned Publisher can complete without emitting any peripherals if there are no known peripherals. Depending on the usage, `collect(_:)` might be an option for converting the output into an array. + /// + /// - Returns: A Publisher that will emit peripherals and then complete. func retrievePeripherals(withIdentifiers identifiers: [UUID]) -> AnyPublisher< BLEPeripheral, BLEError > - /// Retrieve connected peripherals given a set of service identifiers. - /// This method will generate events for any matching peripheral or an error. + /// Returns a list of the peripherals connected to the system whose services match a given set of criteria. + /// + /// The returned Publisher can complete without emitting any peripherals if there are no connected peripherals. Depending on the usage, `collect(_:)` might be an option for converting the output into an array. + /// + /// - Returns: A Publisher that will emit peripherals and then complete. func retrieveConnectedPeripherals(withServices serviceUUIDs: [CBUUID]) -> AnyPublisher< BLEPeripheral, BLEError > @@ -47,19 +53,25 @@ public protocol BLECentralManager: AnyObject { > /// Cancel a peripheral connection. + /// + /// - Returns: A Publisher that emits a completion when the given peripheral is disconnected. func cancelPeripheralConnection(_ peripheral: BLEPeripheral) -> AnyPublisher - /// Register for any connection events. #if os(iOS) || os(tvOS) || os(watchOS) + /// Register for any connection events. func registerForConnectionEvents(options: [CBConnectionEventMatchingOption: Any]?) #endif - /// Observe for any changes to the willRestoreState. - /// This method will generate an event for each update to willRestoreState, if any. + /// Observe for any changes when the system is about to restore the central manager, as part of relaunching the app into the background. + /// + /// This method only applies to apps that opt in to the state preservation and restoration feature of Core Bluetooth. The system invokes this method when relaunching your app into the background to complete some Bluetooth-related task. Use this method to synchronize the state of your app with the state of the Bluetooth system. + /// + /// - Returns: A Publisher that emits a dictionary that contains information about the central manager preserved by the system when it terminated the app. func observeWillRestoreState() -> AnyPublisher<[String: Any], Never> /// Observe any update to the ANCS authorization. - /// This method will trigger an event for any call to the delegate method - /// `didUpdateANCSAuthorizationFor`. + /// This method will trigger an event for any call to the delegate method `centralManager(_:didUpdateANCSAuthorizationFor:)`. + /// + /// - Returns: A Publisher that emits a peripheral whose ANCS authorization status changed. func observeDidUpdateANCSAuthorization() -> AnyPublisher } diff --git a/Sources/BLECombineKit/Central/BLECentralManagerDelegate.swift b/Sources/BLECombineKit/Central/BLECentralManagerDelegate.swift index 15e72ab..657e86a 100644 --- a/Sources/BLECombineKit/Central/BLECentralManagerDelegate.swift +++ b/Sources/BLECombineKit/Central/BLECentralManagerDelegate.swift @@ -13,13 +13,13 @@ typealias DidDiscoverAdvertisementDataResult = ( final class BLECentralManagerDelegate: NSObject, CBCentralManagerDelegate { - let didConnectPeripheral = PassthroughSubject() + let didConnectPeripheral = PassthroughSubject() let didDisconnectPeripheral = PassthroughSubject() let didFailToConnect = PassthroughSubject() let didDiscoverAdvertisementData = PassthroughSubject< DidDiscoverAdvertisementDataResult, BLEError >() - let didUpdateState = PassthroughSubject() + let didUpdateState = PassthroughSubject() let willRestoreState = PassthroughSubject<[String: Any], Never>() let didUpdateANCSAuthorization = PassthroughSubject() @@ -54,8 +54,7 @@ final class BLECentralManagerDelegate: NSObject, CBCentralManagerDelegate { } public func centralManagerDidUpdateState(_ central: CBCentralManager) { - guard let state = ManagerState(rawValue: central.state.rawValue) else { return } - didUpdateState.send(state) + didUpdateState.send(central.state) } public func centralManager( @@ -65,7 +64,7 @@ final class BLECentralManagerDelegate: NSObject, CBCentralManagerDelegate { willRestoreState.send(dict) } - #if !os(macOS) + #if os(iOS) || os(tvOS) || os(watchOS) public func centralManager( _ central: CBCentralManager, didUpdateANCSAuthorizationFor peripheral: CBPeripheral diff --git a/Sources/BLECombineKit/Central/BLEScanResult.swift b/Sources/BLECombineKit/Central/BLEScanResult.swift index f996267..65e490a 100644 --- a/Sources/BLECombineKit/Central/BLEScanResult.swift +++ b/Sources/BLECombineKit/Central/BLEScanResult.swift @@ -8,7 +8,7 @@ import Foundation -public final class BLEScanResult { +public struct BLEScanResult { public let peripheral: BLEPeripheral public let advertisementData: [String: Any] public let rssi: NSNumber diff --git a/Sources/BLECombineKit/Central/CBCentralManagerWrapper.swift b/Sources/BLECombineKit/Central/CBCentralManagerWrapper.swift index ff9ad48..68e8aea 100644 --- a/Sources/BLECombineKit/Central/CBCentralManagerWrapper.swift +++ b/Sources/BLECombineKit/Central/CBCentralManagerWrapper.swift @@ -10,79 +10,71 @@ import CoreBluetooth import Foundation /// Interface for wrapping the CBCentralManager. -/// This interface is critical in order to mock the CBCentralManager calls as Apple has the -/// init restricted. +/// This interface is critical in order to mock the CBCentralManager calls. public protocol CBCentralManagerWrapper { - var manager: CBCentralManager? { get } + /// The CBCentralManager this interface wraps to. + /// Note that CBCentralManager conforms to CBCentralManagerWrapper and this getter interface is a convenient way to avoid an expensive downcast. That is, if you need a fixed reference to the CBCentralManager object do not run `let validManager = manager as? CBCentralManager`, simply run `let validManager = manager.wrappedManager` which will run significantly faster. + var wrappedManager: CBCentralManager? { get } + + /// The delegate object that will receive central events. + var delegate: CBCentralManagerDelegate? { get } + + /// The scanning status of this manager. var isScanning: Bool { get } - func retrievePeripherals(withIdentifiers identifiers: [UUID]) -> [CBPeripheralWrapper] - func retrieveConnectedPeripherals(withServices serviceUUIDs: [CBUUID]) -> [CBPeripheralWrapper] + /// Set up the delegate of the wrapped CBCentralManager. + /// Avoid calling this method unless you explicitly want to listen to delegate events at the cost of breaking the manager's observable events. + func setupDelegate(_ delegate: CBCentralManagerDelegate) + + /// Retrieve peripherals. + func retrieveCBPeripherals(withIdentifiers identifiers: [UUID]) -> [CBPeripheralWrapper] + + /// Retrieve all the connected peripherals. + func retrieveConnectedCBPeripherals(withServices serviceUUIDs: [CBUUID]) -> [CBPeripheralWrapper] + + /// Start scanning for peripherals with the given set of services and options. func scanForPeripherals(withServices serviceUUIDs: [CBUUID]?, options: [String: Any]?) + + /// Stop scanning for peripherals. func stopScan() + + /// Connect to a wrapped peripheral with options. func connect(_ wrappedPeripheral: CBPeripheralWrapper, options: [String: Any]?) + + /// Cancel the connection to a wrapped peripheral. func cancelPeripheralConnection(_ wrappedPeripheral: CBPeripheralWrapper) - #if !os(macOS) + + #if os(iOS) || os(tvOS) || os(watchOS) + /// Register for connection events with options. func registerForConnectionEvents(options: [CBConnectionEventMatchingOption: Any]?) #endif } -final class StandardCBCentralManagerWrapper: CBCentralManagerWrapper { - - var manager: CBCentralManager? { - wrappedManager +extension CBCentralManager: CBCentralManagerWrapper { + public var wrappedManager: CBCentralManager? { + self } - var isScanning: Bool { - wrappedManager.isScanning + public func setupDelegate(_ delegate: CBCentralManagerDelegate) { + self.delegate = delegate } - let wrappedManager: CBCentralManager - - init(with manager: CBCentralManager) { - self.wrappedManager = manager + public func retrieveCBPeripherals(withIdentifiers identifiers: [UUID]) -> [CBPeripheralWrapper] { + return retrievePeripherals(withIdentifiers: identifiers) } - func retrievePeripherals(withIdentifiers identifiers: [UUID]) -> [CBPeripheralWrapper] { - wrappedManager - .retrievePeripherals(withIdentifiers: identifiers) + public func retrieveConnectedCBPeripherals(withServices serviceUUIDs: [CBUUID]) + -> [CBPeripheralWrapper] + { + return retrieveConnectedPeripherals(withServices: serviceUUIDs) } - func retrieveConnectedPeripherals( - withServices serviceUUIDs: [CBUUID] - ) -> [CBPeripheralWrapper] { - wrappedManager - .retrieveConnectedPeripherals(withServices: serviceUUIDs) + public func connect(_ wrappedPeripheral: CBPeripheralWrapper, options: [String: Any]?) { + wrappedPeripheral.connect(manager: self) } - func scanForPeripherals(withServices serviceUUIDs: [CBUUID]?, options: [String: Any]?) { - wrappedManager.scanForPeripherals(withServices: serviceUUIDs, options: options) - } - - func stopScan() { - wrappedManager.stopScan() - } - - func connect(_ wrappedPeripheral: CBPeripheralWrapper, options: [String: Any]?) { - guard let manager else { return } - wrappedPeripheral.connect(manager: manager) - } - - func cancelPeripheralConnection(_ wrappedPeripheral: CBPeripheralWrapper) { - guard let manager else { return } - wrappedPeripheral.cancelConnection(manager: manager) - } - - #if !os(macOS) - func registerForConnectionEvents(options: [CBConnectionEventMatchingOption: Any]?) { - - wrappedManager.registerForConnectionEvents(options: options) - - } - #endif - - func setupDelegate(_ delegate: CBCentralManagerDelegate) { - wrappedManager.delegate = delegate + public func cancelPeripheralConnection(_ wrappedPeripheral: CBPeripheralWrapper) { + wrappedPeripheral.cancelConnection(manager: self) } } diff --git a/Sources/BLECombineKit/Central/ManagerState.swift b/Sources/BLECombineKit/Central/ManagerState.swift deleted file mode 100644 index 7c786bc..0000000 --- a/Sources/BLECombineKit/Central/ManagerState.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// Created by Henry Javier Serrano Echeverria on 1/5/20. -// Copyright © 2020 Henry Serrano. All rights reserved. -// - -import Foundation - -/// Equivalent of `CBManagerState`. -public enum ManagerState: Int { - - case unknown - case resetting - case unsupported - case unauthorized - case poweredOff - case poweredOn - -} diff --git a/Sources/BLECombineKit/Central/StandardBLECentralManager.swift b/Sources/BLECombineKit/Central/StandardBLECentralManager.swift index 9cf19f7..d1d848d 100644 --- a/Sources/BLECombineKit/Central/StandardBLECentralManager.swift +++ b/Sources/BLECombineKit/Central/StandardBLECentralManager.swift @@ -11,48 +11,51 @@ import CoreBluetooth import Foundation final class StandardBLECentralManager: BLECentralManager { - + /// The wrapped CBCentralManager. let associatedCentralManager: CBCentralManagerWrapper - let peripheralProvider: BLEPeripheralProvider - var stateSubject = CurrentValueSubject(ManagerState.unknown) + /// The provider of BLEPeripherals. + lazy var peripheralProvider: BLEPeripheralProvider = StandardBLEPeripheralProvider( + centralManager: self + ) + + /// The Publisher used for tracking the latest state of the wrapped manager. + private(set) var stateSubject = CurrentValueSubject(CBManagerState.unknown) + + /// The delegate to listen to for events. let delegate: BLECentralManagerDelegate + /// The Set used to track all cancellables. private var cancellables = Set() var isScanning: Bool { associatedCentralManager.isScanning } - var state: AnyPublisher { + /// The getter for the state subject. + var state: AnyPublisher { stateSubject.eraseToAnyPublisher() } init( centralManager: CBCentralManagerWrapper, - managerDelegate: BLECentralManagerDelegate = BLECentralManagerDelegate(), - peripheralProvider: BLEPeripheralProvider = StandardBLEPeripheralProvider() + managerDelegate: BLECentralManagerDelegate = BLECentralManagerDelegate() ) { self.associatedCentralManager = centralManager self.delegate = managerDelegate - self.peripheralProvider = peripheralProvider - - if let centralManager = centralManager as? StandardCBCentralManagerWrapper { - centralManager.setupDelegate(managerDelegate) - } + centralManager.setupDelegate(managerDelegate) subscribeToDelegate() } convenience init(with centralManager: CBCentralManager) { - let centralManagerWrapper = StandardCBCentralManagerWrapper(with: centralManager) - self.init(centralManager: centralManagerWrapper, managerDelegate: BLECentralManagerDelegate()) + self.init(centralManager: centralManager, managerDelegate: BLECentralManagerDelegate()) } func retrievePeripherals( withIdentifiers identifiers: [UUID] ) -> AnyPublisher { - let retrievedPeripherals = associatedCentralManager.retrievePeripherals( + let retrievedPeripherals = associatedCentralManager.retrieveCBPeripherals( withIdentifiers: identifiers ) return observePeripherals(from: retrievedPeripherals) @@ -61,7 +64,7 @@ final class StandardBLECentralManager: BLECentralManager { func retrieveConnectedPeripherals( withServices serviceUUIDs: [CBUUID] ) -> AnyPublisher { - let retrievedPeripherals = associatedCentralManager.retrieveConnectedPeripherals( + let retrievedPeripherals = associatedCentralManager.retrieveConnectedCBPeripherals( withServices: serviceUUIDs ) return observePeripherals(from: retrievedPeripherals) @@ -71,22 +74,16 @@ final class StandardBLECentralManager: BLECentralManager { withServices services: [CBUUID]?, options: [String: Any]? ) -> AnyPublisher { + let provider = peripheralProvider let stream = delegate .didDiscoverAdvertisementData - .eraseToAnyPublisher() // Erase needed to silence flatMap(maxPublishers) availability. - .flatMap { [weak self] result -> AnyPublisher in - guard let self else { return Fail(error: BLEError.deallocated).eraseToAnyPublisher() } - let peripheral = self.peripheralProvider.provide( - for: result.peripheral, - centralManager: self + .map { result -> BLEScanResult in + let peripheral = provider.provide(for: result.peripheral) + return BLEScanResult( + peripheral: peripheral, + advertisementData: result.advertisementData, + rssi: result.rssi ) - return Just( - BLEScanResult( - peripheral: peripheral, - advertisementData: result.advertisementData, - rssi: result.rssi - ) - ).setFailureType(to: BLEError.self).eraseToAnyPublisher() } .eraseToAnyPublisher() @@ -106,22 +103,16 @@ final class StandardBLECentralManager: BLECentralManager { peripheral: BLEPeripheral, options: [String: Any]? ) -> AnyPublisher { - associatedCentralManager.connect(peripheral.associatedPeripheral, options: options) + let provider = peripheralProvider + defer { + associatedCentralManager.connect(peripheral.associatedPeripheral, options: options) + } // TODO: merge with didFailToConnect. // TODO: make sure the wrapped peripheral has the same ID as the given peripheral to connect. return delegate .didConnectPeripheral - .setFailureType(to: BLEError.self) - .tryMap { [weak self] wrappedPeripheral in - guard let self else { throw BLEError.deallocated } - let peripheral = self.peripheralProvider.provide( - for: wrappedPeripheral, - centralManager: self - ) - return peripheral - } - .mapError { $0 as? BLEError ?? BLEError.unknown } + .map { provider.provide(for: $0) } .eraseToAnyPublisher() } @@ -129,12 +120,13 @@ final class StandardBLECentralManager: BLECentralManager { _ peripheral: BLEPeripheral ) -> AnyPublisher { let associatedPeripheral = peripheral.associatedPeripheral - associatedCentralManager.cancelPeripheralConnection(associatedPeripheral) + defer { + associatedCentralManager.cancelPeripheralConnection(associatedPeripheral) + } return delegate .didDisconnectPeripheral - .filter { $0.identifier == associatedPeripheral.identifier } - .first() + .first { $0.identifier == associatedPeripheral.identifier } .ignoreOutput() .ignoreFailure() .eraseToAnyPublisher() @@ -151,11 +143,10 @@ final class StandardBLECentralManager: BLECentralManager { } func observeDidUpdateANCSAuthorization() -> AnyPublisher { - delegate.didUpdateANCSAuthorization - .compactMap { [weak self] peripheral in - guard let self = self else { return nil } - return self.peripheralProvider.provide(for: peripheral, centralManager: self) - }.eraseToAnyPublisher() + let provider = peripheralProvider + return delegate.didUpdateANCSAuthorization + .map { provider.provide(for: $0) } + .eraseToAnyPublisher() } // MARK: - Private methods @@ -178,10 +169,10 @@ final class StandardBLECentralManager: BLECentralManager { private func observeDidConnectPeripheral() { delegate .didConnectPeripheral - .sink { [weak self] result in - guard let self = self else { return } - self.peripheralProvider - .provide(for: result, centralManager: self) + .ignoreFailure() + .sink { [peripheralProvider] result in + peripheralProvider + .provide(for: result) .connectionState.send(true) }.store(in: &cancellables) } @@ -190,37 +181,28 @@ final class StandardBLECentralManager: BLECentralManager { delegate .didFailToConnect .ignoreFailure() - .sink { [weak self] result in - guard let self = self else { return } - self.peripheralProvider.provide(for: result, centralManager: self).connectionState.send( - false - ) + .sink { [peripheralProvider] result in + peripheralProvider.provide(for: result) + .connectionState.send(false) }.store(in: &cancellables) } private func observeDidDisconnectPeripheral() { delegate .didDisconnectPeripheral - .sink { [weak self] result in - guard let self = self else { return } - self.peripheralProvider.provide(for: result, centralManager: self).connectionState.send( - false - ) + .sink { [peripheralProvider] result in + peripheralProvider.provide(for: result) + .connectionState.send(false) }.store(in: &cancellables) } private func observePeripherals( from retrievedPeripherals: [CBPeripheralWrapper] ) -> AnyPublisher { + let provider = peripheralProvider let peripherals = retrievedPeripherals - .compactMap { [weak self] peripheral -> BLEPeripheral? in - guard let self = self else { return nil } - return self.peripheralProvider.provide( - for: peripheral, - centralManager: self - ) - } + .map { provider.provide(for: $0) } return Publishers.Sequence.init(sequence: peripherals) .setFailureType(to: BLEError.self) diff --git a/Sources/BLECombineKit/Peripheral Manager/BLEPeripheralManager.swift b/Sources/BLECombineKit/Peripheral Manager/BLEPeripheralManager.swift index c9bd00b..d7e140f 100644 --- a/Sources/BLECombineKit/Peripheral Manager/BLEPeripheralManager.swift +++ b/Sources/BLECombineKit/Peripheral Manager/BLEPeripheralManager.swift @@ -77,16 +77,16 @@ public class BLEPeripheralManager { // MARK: State - public var state: ManagerState { - return ManagerState(rawValue: manager.state.rawValue) ?? .unknown + public var state: CBManagerState { + return manager.state } - public func observeState() -> AnyPublisher { + public func observeState() -> AnyPublisher { return self.delegateWrapper.didUpdateState.eraseToAnyPublisher() } - public func observeStateWithInitialValue() -> AnyPublisher { - return Deferred> { [weak self] in + public func observeStateWithInitialValue() -> AnyPublisher { + return Deferred> { [weak self] in guard let self = self else { return Empty().eraseToAnyPublisher() } @@ -421,7 +421,7 @@ public class BLEPeripheralManager { extension Publisher { - fileprivate func ensure(_ state: ManagerState, manager: BLEPeripheralManager) -> AnyPublisher< + fileprivate func ensure(_ state: CBManagerState, manager: BLEPeripheralManager) -> AnyPublisher< Self.Output, Self.Failure > { Deferred { diff --git a/Sources/BLECombineKit/Peripheral Manager/BLEPeripheralManagerDelegateWrapper.swift b/Sources/BLECombineKit/Peripheral Manager/BLEPeripheralManagerDelegateWrapper.swift index 65ee846..8a303ad 100644 --- a/Sources/BLECombineKit/Peripheral Manager/BLEPeripheralManagerDelegateWrapper.swift +++ b/Sources/BLECombineKit/Peripheral Manager/BLEPeripheralManagerDelegateWrapper.swift @@ -16,7 +16,7 @@ import Foundation /// final class BLEPeripheralManagerDelegateWrapper: NSObject, CBPeripheralManagerDelegate { - let didUpdateState = PassthroughSubject() + let didUpdateState = PassthroughSubject() let isReady = PassthroughSubject() let didStartAdvertising = PassthroughSubject() let didReceiveRead = PassthroughSubject() @@ -37,8 +37,7 @@ final class BLEPeripheralManagerDelegateWrapper: NSObject, CBPeripheralManagerDe } func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { - guard let bleState = ManagerState(rawValue: peripheral.state.rawValue) else { return } - didUpdateState.send(bleState) + didUpdateState.send(peripheral.state) } func peripheralManagerIsReady(toUpdateSubscribers peripheral: CBPeripheralManager) { diff --git a/Sources/BLECombineKit/Peripheral/BLEPeripheralProvider.swift b/Sources/BLECombineKit/Peripheral/BLEPeripheralProvider.swift index dfce541..af55b05 100644 --- a/Sources/BLECombineKit/Peripheral/BLEPeripheralProvider.swift +++ b/Sources/BLECombineKit/Peripheral/BLEPeripheralProvider.swift @@ -10,8 +10,7 @@ import Foundation protocol BLEPeripheralProvider { func provide( - for peripheralWrapper: CBPeripheralWrapper, - centralManager: BLECentralManager + for peripheralWrapper: CBPeripheralWrapper ) -> BLETrackedPeripheral } @@ -24,12 +23,17 @@ final class StandardBLEPeripheralProvider: BLEPeripheralProvider { private lazy var peripherals = [UUID: StandardBLEPeripheral]() + private weak var centralManager: BLECentralManager? + + init(centralManager: BLECentralManager?) { + self.centralManager = centralManager + } + func provide( - for peripheralWrapper: CBPeripheralWrapper, - centralManager: BLECentralManager + for peripheralWrapper: CBPeripheralWrapper ) -> BLETrackedPeripheral { return existingPeripheral(id: peripheralWrapper.identifier) - ?? buildPeripheral(for: peripheralWrapper, centralManager: centralManager) + ?? buildPeripheral(for: peripheralWrapper) } // MARK - Private. @@ -41,8 +45,7 @@ final class StandardBLEPeripheralProvider: BLEPeripheralProvider { } private func buildPeripheral( - for peripheralWrapper: CBPeripheralWrapper, - centralManager: BLECentralManager + for peripheralWrapper: CBPeripheralWrapper ) -> StandardBLEPeripheral { let peripheralDelegate = BLEPeripheralDelegate() peripheralWrapper.setupDelegate(peripheralDelegate) diff --git a/Sources/BLECombineKit/Peripheral/CBPeripheralWrapper.swift b/Sources/BLECombineKit/Peripheral/CBPeripheralWrapper.swift index e46ec48..26c57e2 100644 --- a/Sources/BLECombineKit/Peripheral/CBPeripheralWrapper.swift +++ b/Sources/BLECombineKit/Peripheral/CBPeripheralWrapper.swift @@ -9,21 +9,11 @@ import CoreBluetooth import Foundation -extension CBPeripheral: CBPeripheralWrapper { - public func setupDelegate(_ delegate: CBPeripheralDelegate) { - self.delegate = delegate - } - - public func connect(manager: CBCentralManager) { - manager.connect(self) - } - - public func cancelConnection(manager: CBCentralManager) { - manager.cancelPeripheralConnection(self) - } -} - public protocol CBPeripheralWrapper { + /// The CBCentralManager this interface wraps to. + /// Note that CBPeripheral conforms to CBPeripheralWrapper and this getter interface is a convenient way to avoid an expensive downcast. That is, if you need a fixed reference to the CBPeripheral object do not run `let validPeripheral = peripheral as? CBPeripheral`, simply run `let validPeripheral = peripheral.wrappedPeripheral` which will run significantly faster. + var wrappedPeripheral: CBPeripheral? { get } + /// The state of the wrapped CBPeripheral. var state: CBPeripheralState { get } @@ -37,8 +27,7 @@ public protocol CBPeripheralWrapper { var services: [CBService]? { get } /// Set up the delegate of the wrapped CBPeripheral. - /// Avoid calling this method unless you explicitly want to listen to delegate events at the cost - /// of breaking the peripheral observable events. + /// Avoid calling this method unless you explicitly want to listen to delegate events at the cost of breaking the peripheral observable events. func setupDelegate(_ delegate: CBPeripheralDelegate) /// Connect to a CBCentralManager. @@ -87,3 +76,21 @@ public protocol CBPeripheralWrapper { /// Open an L2CAP channel of the wrapped CBPeripheral. func openL2CAPChannel(_ PSM: CBL2CAPPSM) } + +extension CBPeripheral: CBPeripheralWrapper { + public var wrappedPeripheral: CBPeripheral? { + self + } + + public func setupDelegate(_ delegate: CBPeripheralDelegate) { + self.delegate = delegate + } + + public func connect(manager: CBCentralManager) { + manager.connect(self) + } + + public func cancelConnection(manager: CBCentralManager) { + manager.cancelPeripheralConnection(self) + } +} diff --git a/Tests/BLECombineKitTests/BLECentralManagerTests.swift b/Tests/BLECombineKitTests/BLECentralManagerTests.swift index 55b5c4a..35c5837 100644 --- a/Tests/BLECombineKitTests/BLECentralManagerTests.swift +++ b/Tests/BLECombineKitTests/BLECentralManagerTests.swift @@ -14,7 +14,7 @@ import XCTest final class BLECentralManagerTests: XCTestCase { - var sut: BLECentralManager! + var sut: StandardBLECentralManager! var delegate: BLECentralManagerDelegate! var centralManagerWrapper: MockCBCentralManagerWrapper! var peripheralProvider: MockBLEPeripheralProvider! @@ -27,8 +27,7 @@ final class BLECentralManagerTests: XCTestCase { sut = StandardBLECentralManager( centralManager: centralManagerWrapper, - managerDelegate: delegate, - peripheralProvider: peripheralProvider + managerDelegate: delegate ) } @@ -74,9 +73,9 @@ final class BLECentralManagerTests: XCTestCase { let arrayPeripheralBuilder = MockArrayBLEPeripheralBuilder() sut = StandardBLECentralManager( centralManager: centralManagerWrapper, - managerDelegate: delegate, - peripheralProvider: arrayPeripheralBuilder + managerDelegate: delegate ) + sut.peripheralProvider = arrayPeripheralBuilder let expectation = XCTestExpectation(description: self.debugDescription) let peripheralMock = MockCBPeripheralWrapper() let mockedPeripheral = MockBLEPeripheral() @@ -232,6 +231,7 @@ final class BLECentralManagerTests: XCTestCase { let mockedCBPeripheralWrapper = MockCBPeripheralWrapper() peripheralProvider.blePeripheral = MockBLEPeripheral() var observedPeripheral: BLEPeripheral? + sut.peripheralProvider = peripheralProvider // When sut.observeDidUpdateANCSAuthorization() diff --git a/Tests/BLECombineKitTests/BLEPeripheralProviderTests.swift b/Tests/BLECombineKitTests/BLEPeripheralProviderTests.swift index 14e63d5..e07a119 100644 --- a/Tests/BLECombineKitTests/BLEPeripheralProviderTests.swift +++ b/Tests/BLECombineKitTests/BLEPeripheralProviderTests.swift @@ -18,9 +18,9 @@ final class BLEPeripheralProviderTests: XCTestCase { var centralManager: MockBLECentralManager! override func setUpWithError() throws { - provider = StandardBLEPeripheralProvider() peripheralWrapper = MockCBPeripheralWrapper() centralManager = MockBLECentralManager() + provider = StandardBLEPeripheralProvider(centralManager: centralManager) } override func tearDownWithError() throws { @@ -34,8 +34,8 @@ final class BLEPeripheralProviderTests: XCTestCase { let identifier = peripheralWrapper.identifier // When. - let firstPeripheral = provider.provide(for: peripheralWrapper, centralManager: centralManager) - let secondPeripheral = provider.provide(for: peripheralWrapper, centralManager: centralManager) + let firstPeripheral = provider.provide(for: peripheralWrapper) + let secondPeripheral = provider.provide(for: peripheralWrapper) // Then. let firstAssociatedIdentifier = firstPeripheral.associatedPeripheral.identifier diff --git a/Tests/BLECombineKitTests/Mocks/BLEPeripheralMocks.swift b/Tests/BLECombineKitTests/Mocks/BLEPeripheralMocks.swift index fc5dd68..90a7936 100644 --- a/Tests/BLECombineKitTests/Mocks/BLEPeripheralMocks.swift +++ b/Tests/BLECombineKitTests/Mocks/BLEPeripheralMocks.swift @@ -135,9 +135,7 @@ final class MockBLEPeripheral: BLEPeripheral, BLETrackedPeripheral { } final class MockCBPeripheralWrapper: CBPeripheralWrapper { - - var mockPeripheral: CBPeripheral! - var peripheral: CBPeripheral { mockPeripheral } + var wrappedPeripheral: CBPeripheral? var state = CBPeripheralState.connected diff --git a/Tests/BLECombineKitTests/Mocks/MockBLECentralManager.swift b/Tests/BLECombineKitTests/Mocks/MockBLECentralManager.swift index 3afc23f..300242b 100644 --- a/Tests/BLECombineKitTests/Mocks/MockBLECentralManager.swift +++ b/Tests/BLECombineKitTests/Mocks/MockBLECentralManager.swift @@ -14,8 +14,8 @@ import Foundation final class MockBLECentralManager: BLECentralManager { - private var _state = CurrentValueSubject(ManagerState.unknown) - var state: AnyPublisher { + private var _state = CurrentValueSubject(CBManagerState.unknown) + var state: AnyPublisher { _state.eraseToAnyPublisher() } diff --git a/Tests/BLECombineKitTests/Mocks/MockBLEPeripheralProvider.swift b/Tests/BLECombineKitTests/Mocks/MockBLEPeripheralProvider.swift index 7a80748..8aa8aec 100644 --- a/Tests/BLECombineKitTests/Mocks/MockBLEPeripheralProvider.swift +++ b/Tests/BLECombineKitTests/Mocks/MockBLEPeripheralProvider.swift @@ -15,8 +15,7 @@ final class MockBLEPeripheralProvider: BLEPeripheralProvider { var blePeripheral: BLETrackedPeripheral? func provide( - for peripheral: CBPeripheralWrapper, - centralManager: BLECentralManager + for peripheral: CBPeripheralWrapper ) -> BLETrackedPeripheral { buildBLEPeripheralWasCalledCount += 1 return blePeripheral ?? MockBLEPeripheral() @@ -29,8 +28,7 @@ final class MockArrayBLEPeripheralBuilder: BLEPeripheralProvider { var blePeripherals = [BLETrackedPeripheral]() func provide( - for peripheral: CBPeripheralWrapper, - centralManager: BLECentralManager + for peripheral: CBPeripheralWrapper ) -> BLETrackedPeripheral { let peripheral = blePeripherals.element(at: buildBLEPeripheralWasCalledCount) buildBLEPeripheralWasCalledCount += 1 diff --git a/Tests/BLECombineKitTests/Mocks/MockCBCentralManagerWrapper.swift b/Tests/BLECombineKitTests/Mocks/MockCBCentralManagerWrapper.swift index 47c99db..ff85795 100644 --- a/Tests/BLECombineKitTests/Mocks/MockCBCentralManagerWrapper.swift +++ b/Tests/BLECombineKitTests/Mocks/MockCBCentralManagerWrapper.swift @@ -12,20 +12,28 @@ import Foundation @testable import BLECombineKit final class MockCBCentralManagerWrapper: CBCentralManagerWrapper { - var manager: CBCentralManager? + var wrappedManager: CBCentralManager? var isScanning: Bool = false + var delegate: CBCentralManagerDelegate? + + var setupDelegateWasCalledStack = [CBCentralManagerDelegate]() + func setupDelegate(_ delegate: CBCentralManagerDelegate) { + setupDelegateWasCalledStack.append(delegate) + } + var mockRetrieviePeripherals = [CBPeripheralWrapper]() var retrievePeripheralsWasCalledCount = 0 - func retrievePeripherals(withIdentifiers identifiers: [UUID]) -> [CBPeripheralWrapper] { + func retrieveCBPeripherals(withIdentifiers identifiers: [UUID]) -> [CBPeripheralWrapper] { retrievePeripheralsWasCalledCount += 1 return mockRetrieviePeripherals } var mockRetrieveConnectedPeripherals = [CBPeripheralWrapper]() var retrieveConnectedPeripheralsWasCalledCount = 0 - func retrieveConnectedPeripherals(withServices serviceUUIDs: [CBUUID]) -> [CBPeripheralWrapper] { + func retrieveConnectedCBPeripherals(withServices serviceUUIDs: [CBUUID]) -> [CBPeripheralWrapper] + { retrieveConnectedPeripheralsWasCalledCount += 1 return mockRetrieveConnectedPeripherals }