From b696ce452d7bd0ca117e22510b0fe522e632907f Mon Sep 17 00:00:00 2001 From: Andrea Date: Sun, 28 Mar 2021 20:55:47 +0200 Subject: [PATCH 1/3] Fixing logging for varidic argument --- .../xcschemes/xcschememanagement.plist | 10 +- .../Classes/Model/Loggable.swift | 17 +++- .../CBCentralManagerDelegateProxy.swift | 12 +-- .../Classes/Proxies/CBPeripheralProxy.swift | 30 +++--- Sources/LittleBlueTooth/LittleBlueTooth.swift | 8 +- Tests/LittleBlueToothTests/ListenTest.swift | 93 +++++++++++++++++++ 6 files changed, 137 insertions(+), 33 deletions(-) diff --git a/LittleBlueTooth.xcodeproj/xcuserdata/Andrea.xcuserdatad/xcschemes/xcschememanagement.plist b/LittleBlueTooth.xcodeproj/xcuserdata/Andrea.xcuserdatad/xcschemes/xcschememanagement.plist index d4e902e..bfe9b17 100644 --- a/LittleBlueTooth.xcodeproj/xcuserdata/Andrea.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/LittleBlueTooth.xcodeproj/xcuserdata/Andrea.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ LittleBlueTooth-Package.xcscheme orderHint - 2 + 3 LittleBlueTooth.xcscheme_^#shared#^_ @@ -17,22 +17,22 @@ LittleBlueToothForTest.xcscheme orderHint - 3 + 4 LittleBlueToothPackageDescription.xcscheme_^#shared#^_ orderHint - 4 + 5 LittleBlueToothPackageTests.xcscheme_^#shared#^_ orderHint - 3 + 6 LittleBlueToothTests.xcscheme orderHint - 1 + 2 SuppressBuildableAutocreation diff --git a/Sources/LittleBlueTooth/Classes/Model/Loggable.swift b/Sources/LittleBlueTooth/Classes/Model/Loggable.swift index 3d1de43..876f52d 100644 --- a/Sources/LittleBlueTooth/Classes/Model/Loggable.swift +++ b/Sources/LittleBlueTooth/Classes/Model/Loggable.swift @@ -10,17 +10,28 @@ import os.log protocol Loggable { var isLogEnabled: Bool {get set} - func log(_ message: StaticString, log: OSLog, type: OSLogType, arg: CVarArg...) + func log(_ message: StaticString, log: OSLog, type: OSLogType, arg: [CVarArg]) } extension Loggable { - func log(_ message: StaticString, log: OSLog, type: OSLogType, arg: CVarArg...) { + func log(_ message: StaticString, log: OSLog, type: OSLogType, arg: [CVarArg]) { #if !TEST + // https://stackoverflow.com/questions/50937765/why-does-wrapping-os-log-cause-doubles-to-not-be-logged-correctly/50942917#50942917 guard isLogEnabled else { return } - os_log(type, log: log, message, arg) + assert(arg.count <= 2) + switch arg.count { + case 1: + os_log(type, log: log, message, arg[0]) + case 2: + os_log(type, log: log, message, arg[0], arg[1]) + case 3: + os_log(type, log: log, message, arg[0], arg[1], arg[2]) + default: + os_log(type, log: log, message) + } #endif } } diff --git a/Sources/LittleBlueTooth/Classes/Proxies/CBCentralManagerDelegateProxy.swift b/Sources/LittleBlueTooth/Classes/Proxies/CBCentralManagerDelegateProxy.swift index 41c7c49..ebf6763 100644 --- a/Sources/LittleBlueTooth/Classes/Proxies/CBCentralManagerDelegateProxy.swift +++ b/Sources/LittleBlueTooth/Classes/Proxies/CBCentralManagerDelegateProxy.swift @@ -100,7 +100,7 @@ extension CBCentralManagerDelegateProxy: CBCentralManagerDelegate { log("[LBT: CBCMD] DidUpdateState %{public}d", log: OSLog.LittleBT_Log_CentralManager, type: .debug, - arg: central.state.rawValue) + arg: [central.state.rawValue]) _centralStatePublisher.send(BluetoothState(central.state)) } @@ -109,7 +109,7 @@ extension CBCentralManagerDelegateProxy: CBCentralManagerDelegate { log("[LBT: CBCMD] DidDiscover %{public}@", log: OSLog.LittleBT_Log_CentralManager, type: .debug, - arg: peripheral.description) + arg: [peripheral.description]) let peripheraldiscovery = PeripheralDiscovery(peripheral, advertisement: advertisementData, rssi: RSSI) centralDiscoveriesPublisher.send(peripheraldiscovery) } @@ -119,7 +119,7 @@ extension CBCentralManagerDelegateProxy: CBCentralManagerDelegate { log("[LBT: CBCMD] DidConnect %{public}@", log: OSLog.LittleBT_Log_CentralManager, type: .debug, - arg: didConnect.description) + arg: [didConnect.description]) if isAutoconnectionActive { isAutoconnectionActive = false let event = ConnectionEvent.autoConnected(didConnect) @@ -134,8 +134,8 @@ extension CBCentralManagerDelegateProxy: CBCentralManagerDelegate { log("[LBT: CBCMD] DidDisconnect %{public}@, Error %{public}@", log: OSLog.LittleBT_Log_CentralManager, type: .debug, - arg: didDisconnectPeripheral.description, - error?.localizedDescription ?? "") + arg: [didDisconnectPeripheral.description, + error?.localizedDescription ?? ""]) isAutoconnectionActive = false var lttlError: LittleBluetoothError? if let error = error { @@ -159,7 +159,7 @@ extension CBCentralManagerDelegateProxy: CBCentralManagerDelegate { log("[LBT: CBCMD] WillRestoreState %{public}@", log: OSLog.LittleBT_Log_Restore, type: .debug, - arg: dict.description) + arg: [dict.description]) _willRestoreStatePublisher.send(CentralRestorer(centralManager: central, restoredInfo: dict)) } diff --git a/Sources/LittleBlueTooth/Classes/Proxies/CBPeripheralProxy.swift b/Sources/LittleBlueTooth/Classes/Proxies/CBPeripheralProxy.swift index 0571acf..7d0afaf 100644 --- a/Sources/LittleBlueTooth/Classes/Proxies/CBPeripheralProxy.swift +++ b/Sources/LittleBlueTooth/Classes/Proxies/CBPeripheralProxy.swift @@ -44,7 +44,7 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral){ log("[LBT: CBPD] ReadyToSendWRiteWOResp", log: OSLog.LittleBT_Log_Peripheral, - type: .debug) + type: .debug, arg: []) peripheralIsReadyToSendWriteWithoutResponse.send() } @@ -52,7 +52,7 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { log("[LBT: CBPD] DidUpdateName %{public}@", log: OSLog.LittleBT_Log_Peripheral, type: .debug, - arg: peripheral.name ?? "na") + arg: [peripheral.name ?? "na"]) peripheralChangesPublisher.send(.name(peripheral.name)) } @@ -60,7 +60,7 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { log("[LBT: CBPD] DidModifyServices %{public}@", log: OSLog.LittleBT_Log_Peripheral, type: .debug, - arg: invalidatedServices.description) + arg: [invalidatedServices.description]) peripheralChangesPublisher.send(.invalidatedServices(invalidatedServices)) } @@ -76,7 +76,7 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { log("[LBT: CBPD] DidDiscoverServices, Error %{public}@", log: OSLog.LittleBT_Log_Peripheral, type: .debug, - arg: error?.localizedDescription ?? "") + arg: [error?.localizedDescription ?? "None"]) if let error = error { peripheralDiscoveredServicesPublisher.send((nil,.serviceNotFound(error))) } else { @@ -88,8 +88,8 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { log("[LBT: CBPD] DidDiscoverIncludedServices %{public}@, Error %{public}@", log: OSLog.LittleBT_Log_Peripheral, type: .debug, - arg: service.description, - error?.localizedDescription ?? "") + arg: [service.description, + (error?.localizedDescription ?? "None")]) if let error = error { peripheralDiscoveredIncludedServicesPublisher.send((service, error)) } else { @@ -98,11 +98,11 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?){ - log("[LBT: CBPD] DidDiscoverCharacteristic %{public}@, Error %{public}@", + log("[LBT: CBPD] DidDiscoverCharacteristic %{public}@", log: OSLog.LittleBT_Log_Peripheral, type: .debug, - arg: service.description, - error?.localizedDescription ?? "") + arg: [service.description, + (error?.localizedDescription ?? "None")]) if let error = error { peripheralDiscoveredCharacteristicsForServicePublisher.send((service, .characteristicNotFound(error))) } else { @@ -114,8 +114,8 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { log("[LBT: CBPD] DidUpdateValue %{public}@, Error %{public}@", log: OSLog.LittleBT_Log_Peripheral, type: .debug, - arg: characteristic.description, - error?.localizedDescription ?? "") + arg: [characteristic.description, + (error?.localizedDescription ?? "None")]) if let error = error { peripheralUpdatedValueForCharacteristicPublisher.send((characteristic, .couldNotReadFromCharacteristic(characteristic: characteristic.uuid, error: error))) } else { @@ -131,8 +131,8 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { log("[LBT: CBPD] DidWriteValue %{public}@, Error %{public}@", log: OSLog.LittleBT_Log_Peripheral, type: .debug, - arg: characteristic.description, - error?.localizedDescription ?? "") + arg: [characteristic.description, + (error?.localizedDescription ?? "None")]) if let error = error { peripheralWrittenValueForCharacteristicPublisher.send((characteristic, .couldNotWriteFromCharacteristic(characteristic: characteristic.uuid, error: error))) } else { @@ -144,8 +144,8 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { log("[LBT: CBPD] DidUpdateNotifState %{public}@, Error %{public}@", log: OSLog.LittleBT_Log_Peripheral, type: .debug, - arg: characteristic.description, - error?.localizedDescription ?? "") + arg: [characteristic.description, + (error?.localizedDescription ?? "None")]) if let error = error { peripheralUpdatedNotificationStateForCharacteristicPublisher.send((characteristic, .couldNotUpdateListenState(characteristic: characteristic.uuid, error: error))) } else { diff --git a/Sources/LittleBlueTooth/LittleBlueTooth.swift b/Sources/LittleBlueTooth/LittleBlueTooth.swift index db53466..9506f88 100644 --- a/Sources/LittleBlueTooth/LittleBlueTooth.swift +++ b/Sources/LittleBlueTooth/LittleBlueTooth.swift @@ -196,7 +196,7 @@ public class LittleBlueTooth: Identifiable { "[LBT] init options %{public}@", log: OSLog.LittleBT_Log_General, type: .debug, - arg: configuration.centralManagerOptions?.description ?? "" + arg: [configuration.centralManagerOptions?.description ?? ""] ) } @@ -851,7 +851,7 @@ public class LittleBlueTooth: Identifiable { log("[LBT] Scan restore %{public}@", log: OSLog.LittleBT_Log_Restore, type: .debug, - arg: restorer.centralManager.isScanning ? "true" : "false") + arg: [restorer.centralManager.isScanning ? "true" : "false"]) return .scan(discoveryPublisher: restoreDiscoveryPublisher) } if let periph = restorer.peripherals.first, let cbPeripheral = periph.cbPeripheral { @@ -884,9 +884,9 @@ public class LittleBlueTooth: Identifiable { log("[LBT] Periph restore %{public}@, has delegate: %{public}@ state %{public}d", log: OSLog.LittleBT_Log_Restore, type: .debug, - arg: cbPeripheral.description, + arg: [cbPeripheral.description, cbPeripheral.delegate != nil ? "true" : "false", - cbPeripheral.state.rawValue) + cbPeripheral.state.rawValue]) return Restored.peripheral(self.peripheral!) } return Restored.nothing diff --git a/Tests/LittleBlueToothTests/ListenTest.swift b/Tests/LittleBlueToothTests/ListenTest.swift index 1564964..2e845e6 100644 --- a/Tests/LittleBlueToothTests/ListenTest.swift +++ b/Tests/LittleBlueToothTests/ListenTest.swift @@ -183,6 +183,99 @@ class ListenTest: LittleBlueToothTests { } + func testCombineLatest() { + disposeBag.removeAll() + blinky.simulateProximityChange(.immediate) + let charateristicOne = LittleBlueToothCharacteristic(characteristic: CBMUUID.buttonCharacteristic.uuidString, for: CBMUUID.nordicBlinkyService.uuidString, properties: [.notify, .read]) + let charateristicTwo = LittleBlueToothCharacteristic(characteristic: CBMUUID.ledCharacteristic.uuidString, for: CBMUUID.nordicBlinkyService.uuidString, properties: [.notify, .read, .write]) + + func getOne() -> AnyPublisher { + littleBT.startListen(from: charateristicOne) + .prepend(littleBT.read(from: charateristicTwo)) + .handleEvents(receiveCompletion: { completion in + switch completion { + case .finished: + break + case .failure(let error): + print("Error \(error)") + } + }) + .eraseToAnyPublisher() + } + + func getTwo() -> AnyPublisher { + littleBT.startListen(from: charateristicTwo) + .prepend(littleBT.read(from: charateristicTwo)) + .handleEvents(receiveCompletion: { completion in + switch completion { + case .finished: + break + case .failure(let error): + print("Error \(error)") + } + }) + .eraseToAnyPublisher() + } + let combineLatestListenExpectation = XCTestExpectation(description: "Combine latest expect") + var counter = 0 + let first = Timer.publish(every: 1, on: .main, in: .common).autoconnect() + + first + .map {_ -> UInt8 in + let data = UInt8.random(in: 0...1) + blinky.simulateValueUpdate(Data([data]), for: CBMCharacteristicMock.buttonCharacteristic) + return data + } + .sink { value in + print("Button value:\(value)") + } + .store(in: &disposeBag) + + let second = Timer.publish(every: 1, on: .main, in: .common).autoconnect() + + second + .map {_ -> UInt8 in + let data = UInt8.random(in: 0...1) + blinky.simulateValueUpdate(Data([data]), for: CBMCharacteristicMock.ledCharacteristic) + return data + }.sink { value in + print("Led value:\(value)") + } + .store(in: &disposeBag) + + + StartLittleBlueTooth + .startDiscovery(for: self.littleBT, withServices: nil) + .connect(for: self.littleBT) + .sink(receiveCompletion: { completion in + print("Completion \(completion)") + }) { (answer: Peripheral) in + print("Answer \(answer)") + Publishers.CombineLatest( + getOne(), + getTwo() + ) + .sink(receiveCompletion: { completion in + print("Completion \(completion)") + }) { (answer) in + print("Answer \(answer)") + counter += 1 + if counter >= 10 { + combineLatestListenExpectation.fulfill() + } + } + .store(in: &self.disposeBag) + } + .store(in: &disposeBag) + + + + + wait(for: [combineLatestListenExpectation], timeout: 30) + littleBT.disconnect() + + + } func testListenToMoreCharacteristic() { From f44a90c24c9d36622eb9218a0862bbeb857c7981 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 5 Apr 2021 10:18:01 +0200 Subject: [PATCH 2/3] Combine fixes One time publisher were converted in promises to regulate backpressure. Some publisher where converte in shared to avoid duplication of the subscription graph. --- .../Classes/Model/Peripheral.swift | 480 ++++++++++++------ .../Classes/Proxies/CBPeripheralProxy.swift | 42 +- Sources/LittleBlueTooth/LittleBlueTooth.swift | 65 ++- .../LittleBlueToothTests/CustomOperator.swift | 38 -- Tests/LittleBlueToothTests/ListenTest.swift | 2 +- .../LittleBlueToothTests/WriteReadTest.swift | 45 -- 6 files changed, 386 insertions(+), 286 deletions(-) diff --git a/Sources/LittleBlueTooth/Classes/Model/Peripheral.swift b/Sources/LittleBlueTooth/Classes/Model/Peripheral.swift index c5246f8..dd769cc 100644 --- a/Sources/LittleBlueTooth/Classes/Model/Peripheral.swift +++ b/Sources/LittleBlueTooth/Classes/Model/Peripheral.swift @@ -108,6 +108,7 @@ public class Peripheral: Identifiable { private let peripheralProxy = CBPeripheralDelegateProxy() private var _isLogEnabled: Bool = false + private var disposeBag = [UUID : AnyCancellable]() /// Initialize a `Peripheral` using a `CBperipheral` /// It also attach the publisher to monitor the state of the peripheral @@ -132,24 +133,49 @@ public class Peripheral: Identifiable { #endif } + private func removeAndCancelSubscriber(for key: UUID) { + let sub = disposeBag[key] + sub?.cancel() + disposeBag.removeValue(forKey: key) + } + fileprivate func getService(serviceUUID: CBUUID) -> AnyPublisher<[CBService]?, LittleBluetoothError> { if let services = self.cbPeripheral.services, services.contains(where: { (service) -> Bool in return service.uuid == serviceUUID }) { return Result<[CBService]?, LittleBluetoothError>.Publisher(.success(services)).eraseToAnyPublisher() } else { - let services = self.peripheralProxy.peripheralDiscoveredServicesPublisher - .tryMap { (value) -> [CBService]? in - switch value { - case let (_, error?): - throw error - case let (services?, _) where services.map{$0.uuid}.contains(serviceUUID): - return services - case (_, .none): - throw LittleBluetoothError.serviceNotFound(nil) + let futKey = UUID() + let services = Deferred { + Future<[CBService]?, LittleBluetoothError> { [unowned self, futKey] promise in + self.peripheralProxy.peripheralDiscoveredServicesPublisher + .tryMap { (value) -> [CBService]? in + switch value { + case let (_, error?): + throw error + case let (services?, _) where services.map{$0.uuid}.contains(serviceUUID): + return services + case (_, .none): + throw LittleBluetoothError.serviceNotFound(nil) + } + } + .mapError {$0 as! LittleBluetoothError} + .sink { (completion) in + switch completion { + case .finished: + break + case .failure(let error): + promise(.failure(error)) + self.removeAndCancelSubscriber(for: futKey) + } + } receiveValue: { (charact) in + promise(.success(charact)) + self.removeAndCancelSubscriber(for: futKey) + } + .store(in: &self.disposeBag, for: futKey) } } - .mapError {$0 as! LittleBluetoothError} + .eraseToAnyPublisher() defer { self.cbPeripheral.discoverServices([serviceUUID]) } @@ -158,21 +184,23 @@ public class Peripheral: Identifiable { } fileprivate func getCharateristic(characteristicUUID: CBUUID, from service: CBService) -> AnyPublisher { + // Check if has beed already discovered if let characteristics = service.characteristics, characteristics.contains(where: { (charact) -> Bool in return charact.uuid == characteristicUUID }) { return Result.Publisher(.success(service)).eraseToAnyPublisher() } else { - let charact = self.peripheralProxy.peripheralDiscoveredCharacteristicsForServicePublisher - .tryMap { (value) -> CBService in - switch value { - case let (_, error?): - throw error - case let (service, _): - return service + let charact = + self.peripheralProxy.peripheralDiscoveredCharacteristicsForServicePublisher + .tryMap { (value) -> CBService in + switch value { + case let (_, error?): + throw error + case let (service, _): + return service + } } - } - .mapError {$0 as! LittleBluetoothError} + .mapError {$0 as! LittleBluetoothError} defer { self.cbPeripheral.discoverCharacteristics([characteristicUUID], for: service) } @@ -181,37 +209,85 @@ public class Peripheral: Identifiable { } fileprivate func discoverCharacteristic(_ charateristicUUID: CBUUID, fromService serviceUUID: CBUUID) -> AnyPublisher { - let discovery = self.getService(serviceUUID: serviceUUID) - .customPrint("[LBT] Discover service", isEnabled: isLogEnabled) - .flatMap { services -> AnyPublisher in - let service = services!.filter{ $0.uuid == serviceUUID}.first! - return self.getCharateristic(characteristicUUID: charateristicUUID, from: service) + // Check if it has already been discovered + if let service = cbPeripheral.services?.first(where: { service in + service.uuid == serviceUUID + }), let charact = service.characteristics?.first(where: { characteristic in + characteristic.uuid == charateristicUUID + }) { + return Result.Publisher(.success(charact)).eraseToAnyPublisher() } - .customPrint("[LBT] Discover characteristic", isEnabled: isLogEnabled) - .tryMap { (service) -> CBCharacteristic in - guard let charact = service.characteristics?.filter({ $0.uuid == charateristicUUID}).first else { - throw LittleBluetoothError.characteristicNotFound(nil) + + let futKey = UUID() + let discovery = Deferred { + Future { [unowned self, futKey] promise in + self.getService(serviceUUID: serviceUUID) + .customPrint("[LBT] Discover service", isEnabled: isLogEnabled) + .flatMap { services -> AnyPublisher in + let service = services!.filter{ $0.uuid == serviceUUID}.first! + return self.getCharateristic(characteristicUUID: charateristicUUID, from: service) + } + .customPrint("[LBT] Discover characteristic", isEnabled: isLogEnabled) + .filter { (service) -> Bool in + if let characteristics = service.characteristics?.map({$0.uuid}), characteristics.contains(charateristicUUID) { + return true + } else { + return false + } + } + .map { service -> CBCharacteristic in + return service.characteristics!.filter({ $0.uuid == charateristicUUID}).first! + } + .sink { (completion) in + switch completion { + case .finished: + break + case .failure(let error): + promise(.failure(error)) + self.removeAndCancelSubscriber(for: futKey) + } + } receiveValue: { (charact) in + promise(.success(charact)) + self.removeAndCancelSubscriber(for: futKey) + } + .store(in: &self.disposeBag, for: futKey) } - return charact - } - .mapError{$0 as! LittleBluetoothError} - .eraseToAnyPublisher() + }.eraseToAnyPublisher() + return discovery } func readRSSI() -> AnyPublisher { - let readRSSI = - peripheralProxy.peripheralRSSIPublisher - .tryMap { (value) -> Int in - switch value { - case let (_, error?): - throw error - case let (rssi, _): - return rssi - } + let futKey = UUID() + let readRSSI = Deferred { + Future { [unowned self, futKey] promise in + peripheralProxy.peripheralRSSIPublisher + .tryMap { (value) -> Int in + switch value { + case let (_, error?): + throw error + case let (rssi, _): + return rssi + } + } + .mapError {$0 as! LittleBluetoothError} + .sink(receiveCompletion: { [unowned self, futKey] (completion) in + switch completion { + case .finished: + break + case .failure(let error): + promise(.failure(error)) + self.removeAndCancelSubscriber(for: futKey) + } + }) { [unowned self, futKey] (readvalue) in + promise(.success(readvalue)) + self.removeAndCancelSubscriber(for: futKey) + } + .store(in: &disposeBag, for: futKey) } - .mapError {$0 as! LittleBluetoothError} - .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + defer { cbPeripheral.readRSSI() } @@ -219,23 +295,44 @@ public class Peripheral: Identifiable { } func read(from charateristicUUID: CBUUID, of serviceUUID: CBUUID) -> AnyPublisher { - let read = discoverCharacteristic(charateristicUUID, fromService: serviceUUID) - .flatMap { characteristic -> AnyPublisher in - self.cbPeripheral.readValue(for: characteristic) - return self.peripheralProxy.peripheralUpdatedValueForCharacteristicPublisher - .tryMap { (value) -> CBCharacteristic in - switch value { - case let (_, error?): - throw error - case let (charact, _): - return charact - } + let futKey = UUID() + let read = Deferred { + Future { [unowned self, futKey] promise in + discoverCharacteristic(charateristicUUID, fromService: serviceUUID) + .flatMap { characteristic -> AnyPublisher in + self.cbPeripheral.readValue(for: characteristic) + return self.peripheralProxy.peripheralUpdatedValueForCharacteristicPublisher + .filter { [charUUID = charateristicUUID] (value) in + value.0.uuid == charUUID + } + .tryMap { (value) -> CBCharacteristic in + switch value { + case let (_, error?): + throw error + case let (charact, _): + return charact + } + } + .mapError {$0 as! LittleBluetoothError} + .eraseToAnyPublisher() + } + .map { (characteristic) -> Data? in + characteristic.value + } + .sink(receiveCompletion: { [unowned self, futKey] (completion) in + switch completion { + case .finished: + break + case .failure(let error): + promise(.failure(error)) + self.removeAndCancelSubscriber(for: futKey) + } + }) { [unowned self, futKey] (readvalue) in + promise(.success(readvalue)) + self.removeAndCancelSubscriber(for: futKey) + } + .store(in: &disposeBag, for: futKey) } - .mapError {$0 as! LittleBluetoothError} - .eraseToAnyPublisher() - } - .map { (characteristic) -> Data? in - characteristic.value } .eraseToAnyPublisher() return read @@ -243,37 +340,55 @@ public class Peripheral: Identifiable { func write(to charateristicUUID: CBUUID, of serviceUUID: CBUUID, data: Data, response: Bool = true) -> AnyPublisher { - let write = discoverCharacteristic(charateristicUUID, fromService: serviceUUID) - .flatMap { characteristic -> AnyPublisher in - if response { - self.cbPeripheral.writeValue(data, for: characteristic, type: .withResponse) - return self.peripheralProxy.peripheralWrittenValueForCharacteristicPublisher.tryMap { (value) -> CBCharacteristic in - switch value { - case let (_, error?): - throw error - case let (charact, _): - return charact + + let futKey = UUID() + let write = Deferred { + Future { [unowned self, futKey] promise in + discoverCharacteristic(charateristicUUID, fromService: serviceUUID) + .flatMap { characteristic -> AnyPublisher in + if response { + self.cbPeripheral.writeValue(data, for: characteristic, type: .withResponse) + return self.peripheralProxy.peripheralWrittenValueForCharacteristicPublisher.tryMap { (value) -> CBCharacteristic in + switch value { + case let (_, error?): + throw error + case let (charact, _): + return charact + } + } + .mapError {$0 as! LittleBluetoothError} + .eraseToAnyPublisher() + } else { + let writeWOResp = self.peripheralProxy.peripheralIsReadyToSendWriteWithoutResponse + .map { _ -> Bool in + return true + } + .prepend([self.cbPeripheral.canSendWriteWithoutResponse]) + .filter{ $0 } + .prefix(1) + .map {_ in + self.cbPeripheral.writeValue(data, for: characteristic, type: .withoutResponse) + } + .setFailureType(to: LittleBluetoothError.self) + .map { characteristic } + .eraseToAnyPublisher() + + return writeWOResp + } } - } - .mapError {$0 as! LittleBluetoothError} - .eraseToAnyPublisher() - } else { - - let writeWOResp = self.peripheralProxy.peripheralIsReadyToSendWriteWithoutResponse - .map { _ -> Bool in - return true - } - .prepend([self.cbPeripheral.canSendWriteWithoutResponse]) - .filter{ $0 } - .prefix(1) - .map {_ in - self.cbPeripheral.writeValue(data, for: characteristic, type: .withoutResponse) - } - .setFailureType(to: LittleBluetoothError.self) - .map { characteristic } - .eraseToAnyPublisher() - - return writeWOResp + .sink(receiveCompletion: { [unowned self, futKey] (completion) in + switch completion { + case .finished: + break + case .failure(let error): + promise(.failure(error)) + self.removeAndCancelSubscriber(for: futKey) + } + }) { [unowned self, futKey] (readvalue) in + promise(.success(readvalue)) + self.removeAndCancelSubscriber(for: futKey) + } + .store(in: &disposeBag, for: futKey) } } .eraseToAnyPublisher() @@ -282,50 +397,88 @@ public class Peripheral: Identifiable { func startListen(from charateristicUUID: CBUUID, of serviceUUID: CBUUID) -> AnyPublisher { - let notifyStart = discoverCharacteristic(charateristicUUID, fromService: serviceUUID) - .flatMap { (characteristic) -> AnyPublisher in - if characteristic.isNotifying { - return Result.Publisher(.success(characteristic)).eraseToAnyPublisher() - } - defer { - self.cbPeripheral.setNotifyValue(true, for: characteristic) - } - return self.peripheralProxy.peripheralUpdatedNotificationStateForCharacteristicPublisher - .tryMap { (value) -> CBCharacteristic in - switch value { - case let (_, error?): - throw error - case let (charact, _): - return charact - } + + let futKey = UUID() + let notifyStart = Deferred { + Future { [unowned self, futKey] promise in + discoverCharacteristic(charateristicUUID, fromService: serviceUUID) + .flatMap { (characteristic) -> AnyPublisher in + if characteristic.isNotifying { + return Result.Publisher(.success(characteristic)).eraseToAnyPublisher() + } + defer { + self.cbPeripheral.setNotifyValue(true, for: characteristic) + } + return self.peripheralProxy.peripheralUpdatedNotificationStateForCharacteristicPublisher + .tryMap { (value) -> CBCharacteristic in + switch value { + case let (_, error?): + throw error + case let (charact, _): + return charact + } + } + .mapError {$0 as! LittleBluetoothError} + .eraseToAnyPublisher() + } + .sink { (completion) in + switch completion { + case .finished: + break + case .failure(let error): + promise(.failure(error)) + self.removeAndCancelSubscriber(for: futKey) + } + } receiveValue: { (charact) in + promise(.success(charact)) + self.removeAndCancelSubscriber(for: futKey) + } + .store(in: &self.disposeBag, for: futKey) } - .mapError {$0 as! LittleBluetoothError} - .eraseToAnyPublisher() + } .eraseToAnyPublisher() return notifyStart } func stopListen(from charateristicUUID: CBUUID, of serviceUUID: CBUUID) -> AnyPublisher { - let notifyStop = discoverCharacteristic(charateristicUUID, fromService: serviceUUID) - .flatMap { (characteristic) -> AnyPublisher in - if !characteristic.isNotifying { - return Result.Publisher(.success(characteristic)).eraseToAnyPublisher() - } - defer { - self.cbPeripheral.setNotifyValue(false, for: characteristic) - } - return self.peripheralProxy.peripheralUpdatedNotificationStateForCharacteristicPublisher - .tryMap { (value) -> CBCharacteristic in - switch value { - case let (_, error?): - throw error - case let (charact, _): - return charact - } + let futKey = UUID() + let notifyStop = Deferred { + Future { [unowned self, futKey] promise in + discoverCharacteristic(charateristicUUID, fromService: serviceUUID) + .flatMap { (characteristic) -> AnyPublisher in + if !characteristic.isNotifying { + return Result.Publisher(.success(characteristic)).eraseToAnyPublisher() + } + defer { + self.cbPeripheral.setNotifyValue(false, for: characteristic) + } + return self.peripheralProxy.peripheralUpdatedNotificationStateForCharacteristicPublisher + .tryMap { (value) -> CBCharacteristic in + switch value { + case let (_, error?): + throw error + case let (charact, _): + return charact + } + } + .mapError {$0 as! LittleBluetoothError} + .eraseToAnyPublisher() + } + .sink { (completion) in + switch completion { + case .finished: + break + case .failure(let error): + promise(.failure(error)) + self.removeAndCancelSubscriber(for: futKey) + } + } receiveValue: { (charact) in + promise(.success(charact)) + self.removeAndCancelSubscriber(for: futKey) + } + .store(in: &self.disposeBag, for: futKey) } - .mapError {$0 as! LittleBluetoothError} - .eraseToAnyPublisher() } .eraseToAnyPublisher() return notifyStop @@ -333,37 +486,56 @@ public class Peripheral: Identifiable { func writeAndListen(from charateristicUUID: CBUUID, of serviceUUID: CBUUID, data: Data) -> AnyPublisher { - let writeListen = startListen(from: charateristicUUID, of: serviceUUID) - .flatMap { (_) -> AnyPublisher in - self.write(to: charateristicUUID, of: serviceUUID, data: data) - } - .flatMap { (_) -> AnyPublisher in - self.peripheralProxy.peripheralUpdatedValueForNotifyCharacteristicPublisher - .tryMap { (value) -> CBCharacteristic in - switch value { - case let (_, error?): - throw error - case let (charact, _): - return charact + let futKey = UUID() + let writeListen = Deferred { + Future { [unowned self, futKey] promise in + startListen(from: charateristicUUID, of: serviceUUID) + .flatMap { (_) -> AnyPublisher in + self.write(to: charateristicUUID, of: serviceUUID, data: data) } - } - .mapError {$0 as! LittleBluetoothError} - .eraseToAnyPublisher() - } - .prefix(1) - .filter { (charachteristic) -> Bool in - if charachteristic.uuid == charateristicUUID { - return true - } - return false - } - .flatMap{ (_) -> AnyPublisher in - self.stopListen(from: charateristicUUID, of: serviceUUID) - } - .map { charact -> Data? in - charact.value + .flatMap { (_) -> AnyPublisher in + self.peripheralProxy.peripheralUpdatedValueForNotifyCharacteristicPublisher + .tryMap { (value) -> CBCharacteristic in + switch value { + case let (_, error?): + throw error + case let (charact, _): + return charact + } + } + .mapError {$0 as! LittleBluetoothError} + .eraseToAnyPublisher() + } + .prefix(1) + .filter { (charachteristic) -> Bool in + if charachteristic.uuid == charateristicUUID { + return true + } + return false + } + .flatMap{ (_) -> AnyPublisher in + self.stopListen(from: charateristicUUID, of: serviceUUID) + } + .map { charact -> Data? in + charact.value + } + .sink { (completion) in + switch completion { + case .finished: + break + case .failure(let error): + promise(.failure(error)) + self.removeAndCancelSubscriber(for: futKey) + } + } receiveValue: { (charact) in + promise(.success(charact)) + self.removeAndCancelSubscriber(for: futKey) + } + .store(in: &self.disposeBag, for: futKey) } - .eraseToAnyPublisher() + } + .eraseToAnyPublisher() + return writeListen } diff --git a/Sources/LittleBlueTooth/Classes/Proxies/CBPeripheralProxy.swift b/Sources/LittleBlueTooth/Classes/Proxies/CBPeripheralProxy.swift index 7d0afaf..91c58fe 100644 --- a/Sources/LittleBlueTooth/Classes/Proxies/CBPeripheralProxy.swift +++ b/Sources/LittleBlueTooth/Classes/Proxies/CBPeripheralProxy.swift @@ -19,16 +19,28 @@ class CBPeripheralDelegateProxy: NSObject { let peripheralChangesPublisher = PassthroughSubject() let peripheralRSSIPublisher = PassthroughSubject<(Int, LittleBluetoothError?), Never>() - let peripheralDiscoveredServicesPublisher = PassthroughSubject<([CBService]?, LittleBluetoothError?), Never>() - let peripheralDiscoveredIncludedServicesPublisher = PassthroughSubject<(CBService, Error?), Never>() - let peripheralDiscoveredCharacteristicsForServicePublisher = PassthroughSubject<(CBService, LittleBluetoothError?), Never>() + + lazy var peripheralDiscoveredServicesPublisher = { _peripheralDiscoveredServicesPublisher.share().eraseToAnyPublisher() + }() + let _peripheralDiscoveredServicesPublisher = PassthroughSubject<([CBService]?, LittleBluetoothError?), Never>() + + lazy var peripheralDiscoveredIncludedServicesPublisher = { _peripheralDiscoveredIncludedServicesPublisher.share().eraseToAnyPublisher() + }() + let _peripheralDiscoveredIncludedServicesPublisher = PassthroughSubject<(CBService, Error?), Never>() + + lazy var peripheralDiscoveredCharacteristicsForServicePublisher = { _peripheralDiscoveredCharacteristicsForServicePublisher.share().eraseToAnyPublisher() + }() + let _peripheralDiscoveredCharacteristicsForServicePublisher = PassthroughSubject<(CBService, LittleBluetoothError?), Never>() + + lazy var peripheralUpdatedNotificationStateForCharacteristicPublisher = { _peripheralUpdatedNotificationStateForCharacteristicPublisher.share().eraseToAnyPublisher() + }() + let _peripheralUpdatedNotificationStateForCharacteristicPublisher = + PassthroughSubject<(CBCharacteristic, LittleBluetoothError?), Never>() + let peripheralUpdatedValueForCharacteristicPublisher = PassthroughSubject<(CBCharacteristic, LittleBluetoothError?), Never>() let peripheralUpdatedValueForNotifyCharacteristicPublisher = PassthroughSubject<(CBCharacteristic, LittleBluetoothError?), Never>() let peripheralWrittenValueForCharacteristicPublisher = PassthroughSubject<(CBCharacteristic, LittleBluetoothError?), Never>() let peripheralIsReadyToSendWriteWithoutResponse = PassthroughSubject() - - let peripheralUpdatedNotificationStateForCharacteristicPublisher = - PassthroughSubject<(CBCharacteristic, LittleBluetoothError?), Never>() let peripheralDiscoveredDescriptorsForCharacteristicPublisher = PassthroughSubject<(CBCharacteristic, LittleBluetoothError?), Never>() @@ -78,9 +90,9 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { type: .debug, arg: [error?.localizedDescription ?? "None"]) if let error = error { - peripheralDiscoveredServicesPublisher.send((nil,.serviceNotFound(error))) + _peripheralDiscoveredServicesPublisher.send((nil,.serviceNotFound(error))) } else { - peripheralDiscoveredServicesPublisher.send((peripheral.services, nil)) + _peripheralDiscoveredServicesPublisher.send((peripheral.services, nil)) } } @@ -91,22 +103,22 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { arg: [service.description, (error?.localizedDescription ?? "None")]) if let error = error { - peripheralDiscoveredIncludedServicesPublisher.send((service, error)) + _peripheralDiscoveredIncludedServicesPublisher.send((service, error)) } else { - peripheralDiscoveredIncludedServicesPublisher.send((service, nil)) + _peripheralDiscoveredIncludedServicesPublisher.send((service, nil)) } } func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?){ - log("[LBT: CBPD] DidDiscoverCharacteristic %{public}@", + log("[LBT: CBPD] DidDiscoverCharacteristic %{public}@, Error %{public}@", log: OSLog.LittleBT_Log_Peripheral, type: .debug, arg: [service.description, (error?.localizedDescription ?? "None")]) if let error = error { - peripheralDiscoveredCharacteristicsForServicePublisher.send((service, .characteristicNotFound(error))) + _peripheralDiscoveredCharacteristicsForServicePublisher.send((service, .characteristicNotFound(error))) } else { - peripheralDiscoveredCharacteristicsForServicePublisher.send((service, nil)) + _peripheralDiscoveredCharacteristicsForServicePublisher.send((service, nil)) } } @@ -147,9 +159,9 @@ extension CBPeripheralDelegateProxy: CBPeripheralDelegate { arg: [characteristic.description, (error?.localizedDescription ?? "None")]) if let error = error { - peripheralUpdatedNotificationStateForCharacteristicPublisher.send((characteristic, .couldNotUpdateListenState(characteristic: characteristic.uuid, error: error))) + _peripheralUpdatedNotificationStateForCharacteristicPublisher.send((characteristic, .couldNotUpdateListenState(characteristic: characteristic.uuid, error: error))) } else { - peripheralUpdatedNotificationStateForCharacteristicPublisher.send((characteristic, nil)) + _peripheralUpdatedNotificationStateForCharacteristicPublisher.send((characteristic, nil)) } } diff --git a/Sources/LittleBlueTooth/LittleBlueTooth.swift b/Sources/LittleBlueTooth/LittleBlueTooth.swift index 9506f88..7de47a5 100644 --- a/Sources/LittleBlueTooth/LittleBlueTooth.swift +++ b/Sources/LittleBlueTooth/LittleBlueTooth.swift @@ -473,42 +473,41 @@ public class LittleBlueTooth: Identifiable { public func read(from characteristic: LittleBlueToothCharacteristic) -> AnyPublisher { let readSubject = PassthroughSubject() - let key = UUID() - + let futKey = UUID() ensureBluetoothState() - .customPrint("[LBT] ReadPublisher", isEnabled: isLogEnabled) - .flatMap { [unowned self] _ in - self.ensurePeripheralReady() - } - .flatMap { periph in - periph.read(from: characteristic.id, of: characteristic.service) - } - .tryMap { (data) -> T in - guard let data = data else { - throw LittleBluetoothError.emptyData + .customPrint("[LBT] ReadPublisher", isEnabled: isLogEnabled) + .flatMap { [unowned self] _ in + self.ensurePeripheralReady() } - return try T.init(from: data) - } - .mapError { (error) -> LittleBluetoothError in - if let er = error as? LittleBluetoothError { - return er + .flatMap { [characteristic] periph in + periph.read(from: characteristic.id, of: characteristic.service) } - return .couldNotReadFromCharacteristic(characteristic: characteristic.id, error: error) - } - .sink(receiveCompletion: { [unowned self, key] (completion) in - switch completion { - case .finished: - break - case .failure(let error): - readSubject.send(completion: .failure(error)) - self.removeAndCancelSubscriber(for: key) + .tryMap { (data) -> T in + guard let data = data else { + throw LittleBluetoothError.emptyData + } + return try T.init(from: data) } - }) { [unowned self, key] (readvalue) in - readSubject.send(readvalue) - readSubject.send(completion: .finished) - self.removeAndCancelSubscriber(for: key) - } - .store(in: &disposeBag, for: key) + .mapError { (error) -> LittleBluetoothError in + if let er = error as? LittleBluetoothError { + return er + } + return .couldNotReadFromCharacteristic(characteristic: characteristic.id, error: error) + } + .sink(receiveCompletion: { [unowned self, futKey] (completion) in + switch completion { + case .finished: + break + case .failure(let error): + readSubject.send(completion: .failure(error)) + self.removeAndCancelSubscriber(for: futKey) + } + }) { [unowned self, futKey] (readvalue) in + readSubject.send(readvalue) + readSubject.send(completion: .finished) + self.removeAndCancelSubscriber(for: futKey) + } + .store(in: &disposeBag, for: futKey) return readSubject.eraseToAnyPublisher() } @@ -932,7 +931,7 @@ public class LittleBlueTooth: Identifiable { prom(.failure(error)) self.removeAndCancelSubscriber(for: futKey) } - }) { [unowned self] (state) in + }) { [unowned self, futKey] (state) in prom(.success(state)) self.removeAndCancelSubscriber(for: futKey) } diff --git a/Tests/LittleBlueToothTests/CustomOperator.swift b/Tests/LittleBlueToothTests/CustomOperator.swift index 9f76f88..886046b 100644 --- a/Tests/LittleBlueToothTests/CustomOperator.swift +++ b/Tests/LittleBlueToothTests/CustomOperator.swift @@ -216,44 +216,6 @@ class CustomOperator: LittleBlueToothTests { XCTAssert(ledState!.isOn == false) } - /// Read custom operator test fail for wrong charcteriscti - func testWrongCharacteristicErrorOperator() { - disposeBag.removeAll() - - blinky.simulateProximityChange(.immediate) - let charateristic = LittleBlueToothCharacteristic(characteristic: "00001525-1212-EFDE-1523-785FEABCD133", for: CBUUID.nordicBlinkyService.uuidString, properties: [.read, .notify, .write]) - let wrongCharacteristicExpectation = expectation(description: "Wrong characteristic expectation") - - var isWrong = false - - StartLittleBlueTooth - .startDiscovery(for: self.littleBT, withServices: nil) - .connect(for: self.littleBT) - .read(for: self.littleBT, from: charateristic) - .sink(receiveCompletion: { completion in - print("Completion \(completion)") - switch completion { - case .finished: - break - case let .failure(error): - if case LittleBluetoothError.characteristicNotFound(_) = error { - isWrong = true - self.littleBT.disconnect().sink(receiveCompletion: {_ in - }) { (_) in - wrongCharacteristicExpectation.fulfill() - } - .store(in: &self.disposeBag) - } - } - }) { (answer: LedState) in - print("Answer \(answer)") - } - .store(in: &disposeBag) - - waitForExpectations(timeout: 10) - XCTAssert(isWrong) - } - /// Write custom operator test func testWriteLedOnReadLedONOperator() { disposeBag.removeAll() diff --git a/Tests/LittleBlueToothTests/ListenTest.swift b/Tests/LittleBlueToothTests/ListenTest.swift index 2e845e6..51d20d5 100644 --- a/Tests/LittleBlueToothTests/ListenTest.swift +++ b/Tests/LittleBlueToothTests/ListenTest.swift @@ -191,7 +191,7 @@ class ListenTest: LittleBlueToothTests { func getOne() -> AnyPublisher { littleBT.startListen(from: charateristicOne) - .prepend(littleBT.read(from: charateristicTwo)) + .prepend(littleBT.read(from: charateristicOne)) .handleEvents(receiveCompletion: { completion in switch completion { case .finished: diff --git a/Tests/LittleBlueToothTests/WriteReadTest.swift b/Tests/LittleBlueToothTests/WriteReadTest.swift index 38d7023..b6a9625 100644 --- a/Tests/LittleBlueToothTests/WriteReadTest.swift +++ b/Tests/LittleBlueToothTests/WriteReadTest.swift @@ -82,51 +82,6 @@ class ReadWriteTest: LittleBlueToothTests { XCTAssert(isWrong) } - - func testWrongCharacteristicError() { - disposeBag.removeAll() - - blinky.simulateProximityChange(.immediate) - let charateristic = LittleBlueToothCharacteristic(characteristic: "00001525-1212-EFDE-1523-785FEABCD133", for: CBUUID.nordicBlinkyService.uuidString, properties: [.read, .notify, .write]) - let wrongCharacteristicExpectation = expectation(description: "Wrong characteristic expectation") - - var isWrong = false - - littleBT.startDiscovery(withServices: nil, options: [CBCentralManagerScanOptionAllowDuplicatesKey : false]) - .map { disc -> PeripheralDiscovery in - print("Discovery discovery \(disc)") - return disc - } - .flatMap { discovery in - self.littleBT.connect(to: discovery) - } - .flatMap { _ -> AnyPublisher in - self.littleBT.read(from: charateristic) - } - .sink(receiveCompletion: { completion in - print("Completion \(completion)") - switch completion { - case .finished: - break - case let .failure(error): - if case LittleBluetoothError.characteristicNotFound(_) = error { - isWrong = true - self.littleBT.disconnect().sink(receiveCompletion: {_ in - }) { (_) in - wrongCharacteristicExpectation.fulfill() - } - .store(in: &self.disposeBag) - } - } - }) { (answer) in - print("Answer \(answer)") - } - .store(in: &disposeBag) - - waitForExpectations(timeout: 10) - XCTAssert(isWrong) - } - func testReadLedOFF() { blinky.simulateReset() disposeBag.removeAll() From d4db1c5b055f39dc6ca6e2e5af4ef06ac80e9706 Mon Sep 17 00:00:00 2001 From: Andrea Date: Mon, 5 Apr 2021 10:44:35 +0200 Subject: [PATCH 3/3] Fixed tests --- Tests/LittleBlueToothTests/ListenTest.swift | 41 ++++++--------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/Tests/LittleBlueToothTests/ListenTest.swift b/Tests/LittleBlueToothTests/ListenTest.swift index 51d20d5..d6c1658 100644 --- a/Tests/LittleBlueToothTests/ListenTest.swift +++ b/Tests/LittleBlueToothTests/ListenTest.swift @@ -192,35 +192,19 @@ class ListenTest: LittleBlueToothTests { func getOne() -> AnyPublisher { littleBT.startListen(from: charateristicOne) .prepend(littleBT.read(from: charateristicOne)) - .handleEvents(receiveCompletion: { completion in - switch completion { - case .finished: - break - case .failure(let error): - print("Error \(error)") - } - }) - .eraseToAnyPublisher() - } + .eraseToAnyPublisher() + } func getTwo() -> AnyPublisher { littleBT.startListen(from: charateristicTwo) .prepend(littleBT.read(from: charateristicTwo)) - .handleEvents(receiveCompletion: { completion in - switch completion { - case .finished: - break - case .failure(let error): - print("Error \(error)") - } - }) .eraseToAnyPublisher() } let combineLatestListenExpectation = XCTestExpectation(description: "Combine latest expect") var counter = 0 let first = Timer.publish(every: 1, on: .main, in: .common).autoconnect() - first + let firstScheduler = first .map {_ -> UInt8 in let data = UInt8.random(in: 0...1) blinky.simulateValueUpdate(Data([data]), for: CBMCharacteristicMock.buttonCharacteristic) @@ -229,11 +213,10 @@ class ListenTest: LittleBlueToothTests { .sink { value in print("Button value:\(value)") } - .store(in: &disposeBag) let second = Timer.publish(every: 1, on: .main, in: .common).autoconnect() - second + let secondScheduler = second .map {_ -> UInt8 in let data = UInt8.random(in: 0...1) blinky.simulateValueUpdate(Data([data]), for: CBMCharacteristicMock.ledCharacteristic) @@ -241,7 +224,6 @@ class ListenTest: LittleBlueToothTests { }.sink { value in print("Led value:\(value)") } - .store(in: &disposeBag) StartLittleBlueTooth @@ -261,20 +243,21 @@ class ListenTest: LittleBlueToothTests { print("Answer \(answer)") counter += 1 if counter >= 10 { - combineLatestListenExpectation.fulfill() + self.littleBT.disconnect().sink(receiveCompletion: {_ in + }) { (_) in + combineLatestListenExpectation.fulfill() + secondScheduler.cancel() + firstScheduler.cancel() + } + .store(in: &self.disposeBag) } } .store(in: &self.disposeBag) } .store(in: &disposeBag) - - - wait(for: [combineLatestListenExpectation], timeout: 30) - littleBT.disconnect() - - + }