diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d96fbe1..4d8f228a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 3.0.0 +* iOS/macOS: complete concurrent reads, descriptor operations, notification changes, and RSSI reads one callback at a time. + ## 2.3.0 * Windows: support connectionless manufacturer-data advertising without a GATT service, including state/error reporting and cleanup on stop/disposal. * Apple: Accurately report manufacturer-data advertising capabilities. diff --git a/darwin/universal_ble/Sources/universal_ble/UniversalBleHelper.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBleHelper.swift index 64ac123d..23df66af 100644 --- a/darwin/universal_ble/Sources/universal_ble/UniversalBleHelper.swift +++ b/darwin/universal_ble/Sources/universal_ble/UniversalBleHelper.swift @@ -198,6 +198,13 @@ protocol DeviceFuture { func fail(with error: Error) } +extension Array { + mutating func popFirst(where predicate: (Element) throws -> Bool) rethrows -> Element? { + guard let index = try firstIndex(where: predicate) else { return nil } + return remove(at: index) + } +} + extension Array where Element: DeviceFuture { mutating func failAndRemoveAll(matching deviceId: String, with error: Error) { removeAll { future in diff --git a/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift b/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift index 33a54c7f..8ad20d34 100644 --- a/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift +++ b/darwin/universal_ble/Sources/universal_ble/UniversalBlePlugin.swift @@ -725,66 +725,66 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral } public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) { - descriptorReadFutures.removeAll { future in - if future.deviceId == peripheral.uuid.uuidString && - future.descriptorId == descriptor.uuid.uuidStr && - future.characteristicId == descriptor.characteristic?.uuid.uuidStr && - future.serviceId == descriptor.characteristic?.service?.uuid.uuidStr { - if let flutterError = error?.toFlutterError() { - UniversalBleLogger.shared.logError("READ_DESCRIPTOR_FAILED <- \(peripheral.uuid.uuidString) \(descriptor.uuid.uuidStr): \(flutterError.message ?? "")") - future.result(Result.failure(flutterError)) - } else { - if let valueData = descriptor.value as? Data { - future.result(Result.success(FlutterStandardTypedData(bytes: valueData))) - } else if let numberVal = descriptor.value as? NSNumber { - var val = numberVal.uint16Value - let data = Data(bytes: &val, count: MemoryLayout.size) - future.result(Result.success(FlutterStandardTypedData(bytes: data))) - } else if let stringVal = descriptor.value as? String { - let data = Data(stringVal.utf8) - future.result(Result.success(FlutterStandardTypedData(bytes: data))) - } else if let cbuuid = descriptor.value as? CBUUID { - future.result(Result.success(FlutterStandardTypedData(bytes: cbuuid.data))) - } else { - future.result(Result.success(FlutterStandardTypedData(bytes: Data()))) - } - } - return true - } - return false + guard let future = descriptorReadFutures.popFirst(where: { + $0.deviceId == peripheral.uuid.uuidString && + $0.descriptorId == descriptor.uuid.uuidStr && + $0.characteristicId == descriptor.characteristic?.uuid.uuidStr && + $0.serviceId == descriptor.characteristic?.service?.uuid.uuidStr + }) else { + return + } + + if let flutterError = error?.toFlutterError() { + UniversalBleLogger.shared.logError("READ_DESCRIPTOR_FAILED <- \(peripheral.uuid.uuidString) \(descriptor.uuid.uuidStr): \(flutterError.message ?? "")") + future.result(Result.failure(flutterError)) + } else if let valueData = descriptor.value as? Data { + future.result(Result.success(FlutterStandardTypedData(bytes: valueData))) + } else if let numberVal = descriptor.value as? NSNumber { + var val = numberVal.uint16Value + let data = Data(bytes: &val, count: MemoryLayout.size) + future.result(Result.success(FlutterStandardTypedData(bytes: data))) + } else if let stringVal = descriptor.value as? String { + let data = Data(stringVal.utf8) + future.result(Result.success(FlutterStandardTypedData(bytes: data))) + } else if let cbuuid = descriptor.value as? CBUUID { + future.result(Result.success(FlutterStandardTypedData(bytes: cbuuid.data))) + } else { + future.result(Result.success(FlutterStandardTypedData(bytes: Data()))) } } public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) { - descriptorWriteFutures.removeAll { future in - if future.deviceId == peripheral.uuid.uuidString && - future.descriptorId == descriptor.uuid.uuidStr && - future.characteristicId == descriptor.characteristic?.uuid.uuidStr && - future.serviceId == descriptor.characteristic?.service?.uuid.uuidStr { - if let flutterError = error?.toFlutterError() { - UniversalBleLogger.shared.logError("WRITE_DESCRIPTOR_FAILED <- \(peripheral.uuid.uuidString) \(descriptor.uuid.uuidStr): \(flutterError.message ?? "")") - future.result(Result.failure(flutterError)) - } else { - future.result(Result.success({}())) - } - return true - } - return false + guard let future = descriptorWriteFutures.popFirst(where: { + $0.deviceId == peripheral.uuid.uuidString && + $0.descriptorId == descriptor.uuid.uuidStr && + $0.characteristicId == descriptor.characteristic?.uuid.uuidStr && + $0.serviceId == descriptor.characteristic?.service?.uuid.uuidStr + }) else { + return + } + + if let flutterError = error?.toFlutterError() { + UniversalBleLogger.shared.logError("WRITE_DESCRIPTOR_FAILED <- \(peripheral.uuid.uuidString) \(descriptor.uuid.uuidStr): \(flutterError.message ?? "")") + future.result(Result.failure(flutterError)) + } else { + future.result(Result.success({}())) } } public func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) { - characteristicNotifyFutures.removeAll { future in - if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr { - if let flutterError = error?.toFlutterError() { - UniversalBleLogger.shared.logError("SET_NOTIFY_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")") - future.result(Result.failure(flutterError)) - } else { - future.result(Result.success({}())) - } - return true - } - return false + guard let future = characteristicNotifyFutures.popFirst(where: { + $0.deviceId == peripheral.uuid.uuidString && + $0.characteristicId == characteristic.uuid.uuidStr && + $0.serviceId == characteristic.service?.uuid.uuidStr + }) else { + return + } + + if let flutterError = error?.toFlutterError() { + UniversalBleLogger.shared.logError("SET_NOTIFY_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")") + future.result(Result.failure(flutterError)) + } else { + future.result(Result.success({}())) } } @@ -826,37 +826,36 @@ private class BleCentralDarwin: NSObject, UniversalBlePlatformChannel, CBCentral return } - // Update futures for readValue - characteristicReadFutures.removeAll { future in - if future.deviceId == peripheral.uuid.uuidString && future.characteristicId == characteristic.uuid.uuidStr && future.serviceId == characteristic.service?.uuid.uuidStr { - if let flutterError = error?.toFlutterError() { - UniversalBleLogger.shared.logError("READ_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")") - future.result(Result.failure(flutterError)) - } else { - if let characteristicValue = characteristic.value { - future.result(Result.success(FlutterStandardTypedData(bytes: characteristicValue))) - } else { - future.result(Result.failure(createFlutterError(code: .readFailed, message: "No value"))) - } - } - return true - } - return false + guard let future = characteristicReadFutures.popFirst(where: { + $0.deviceId == peripheral.uuid.uuidString && + $0.characteristicId == characteristic.uuid.uuidStr && + $0.serviceId == characteristic.service?.uuid.uuidStr + }) else { + return + } + + if let flutterError = error?.toFlutterError() { + UniversalBleLogger.shared.logError("READ_FAILED <- \(peripheral.uuid.uuidString) \(characteristic.uuid.uuidStr): \(flutterError.message ?? "")") + future.result(Result.failure(flutterError)) + } else if let characteristicValue = characteristic.value { + future.result(Result.success(FlutterStandardTypedData(bytes: characteristicValue))) + } else { + future.result(Result.failure(createFlutterError(code: .readFailed, message: "No value"))) } } public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) { - rssiReadFutures.removeAll { future in - if future.deviceId == peripheral.uuid.uuidString { - if let flutterError = error?.toFlutterError() { - UniversalBleLogger.shared.logError("READ_RSSI_FAILED <- \(peripheral.uuid.uuidString): \(flutterError.message ?? "")") - future.result(Result.failure(flutterError)) - } else { - future.result(Result.success(RSSI.int64Value)) - } - return true - } - return false + guard let future = rssiReadFutures.popFirst(where: { + $0.deviceId == peripheral.uuid.uuidString + }) else { + return + } + + if let flutterError = error?.toFlutterError() { + UniversalBleLogger.shared.logError("READ_RSSI_FAILED <- \(peripheral.uuid.uuidString): \(flutterError.message ?? "")") + future.result(Result.failure(flutterError)) + } else { + future.result(Result.success(RSSI.int64Value)) } } }