Skip to content

Commit b4fe671

Browse files
committed
[BUGFIX] Fix: Write without response may get stuck if sending a large amount of data one by one.
1 parent dc88bb9 commit b4fe671

File tree

3 files changed

+48
-10
lines changed

3 files changed

+48
-10
lines changed

Sources/Peripheral/Peripheral.swift

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ public final class Peripheral: Sendable {
5555
#endif
5656
}
5757

58+
public var canSendWriteWithoutResponse: Bool {
59+
cbPeripheral.canSendWriteWithoutResponse
60+
}
61+
62+
public var isReadyToSendWriteWithoutResponse: AnyPublisher<Void, Never> {
63+
context.isReadyToSendWriteWithoutResponse.eraseToAnyPublisher()
64+
}
65+
5866
public let cbPeripheral: CBPeripheral
5967

6068
private var context: PeripheralContext {
@@ -151,16 +159,25 @@ public final class Peripheral: Sendable {
151159

152160
/// Writes the value of a characteristic.
153161
public func writeValue(_ data: Data, for characteristic: Characteristic, type: CBCharacteristicWriteType) async throws {
154-
try await self.context.writeCharacteristicValueExecutor.enqueue(withKey: characteristic.uuid) { [weak self] in
155-
guard let self = self else { return }
156-
157-
self.cbPeripheral.writeValue(data, for: characteristic.cbCharacteristic, type: type)
158-
159-
guard type == .withoutResponse else {
162+
switch type {
163+
case .withResponse:
164+
try await self.context.writeCharacteristicValueExecutor.enqueue(withKey: characteristic.uuid) { [weak self] in
165+
guard let self = self else { return }
166+
167+
self.cbPeripheral.writeValue(data, for: characteristic.cbCharacteristic, type: type)
168+
}
169+
case .withoutResponse:
170+
if self.cbPeripheral.canSendWriteWithoutResponse {
171+
self.cbPeripheral.writeValue(data, for: characteristic.cbCharacteristic, type: type)
160172
return
161173
}
162-
163-
self.cbPeripheralDelegate.peripheral(self.cbPeripheral, didWriteValueFor: characteristic.cbCharacteristic, error: nil)
174+
try await self.context.writeWithoutResponseCharacteristicValueExecutor.enqueue { [weak self] in
175+
guard let self = self else { return }
176+
177+
self.cbPeripheral.writeValue(data, for: characteristic.cbCharacteristic, type: type)
178+
}
179+
@unknown default:
180+
preconditionFailure("Unhandled CBCharacteristicWriteType: \(type)")
164181
}
165182
}
166183

Sources/Peripheral/PeripheralContext.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ import Combine
88
actor PeripheralContext {
99
nonisolated let characteristicValueUpdatedSubject = PassthroughSubject<CharacteristicValueUpdateEventData, Never>()
1010
nonisolated let invalidatedServicesSubject = PassthroughSubject<[Service], Never>()
11-
11+
nonisolated let isReadyToSendWriteWithoutResponse = PassthroughSubject<Void, Never>()
12+
let serialExecutor = SerialExecutor()
13+
1214
private(set) lazy var readRSSIExecutor = {
1315
let executor = AsyncSerialExecutor<NSNumber>()
1416
Task {
@@ -46,7 +48,13 @@ actor PeripheralContext {
4648
flushableExecutors.append(executor)
4749
return executor
4850
}()
49-
51+
52+
private(set) lazy var writeWithoutResponseCharacteristicValueExecutor = {
53+
let executor = AsyncSerialExecutor<Void>()
54+
flushableExecutors.append(executor)
55+
return executor
56+
}()
57+
5058
private(set) lazy var setNotifyValueExecutor = {
5159
let executor = AsyncExecutorMap<CBUUID, Void>()
5260
flushableExecutors.append(executor)

Sources/Peripheral/PeripheralDelegate.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,19 @@ extension PeripheralDelegate: CBPeripheralDelegate {
9797
}
9898
}
9999

100+
func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
101+
Self.logger.info("\(#function), \(peripheral)")
102+
context.serialExecutor.enqueue { [weak self] in
103+
guard let self else { return }
104+
do {
105+
try await context.writeWithoutResponseCharacteristicValueExecutor.setWorkCompletedWithResult(.success(()))
106+
} catch {
107+
Self.logger.warning("Received peripheralIsReadyToSendWriteWithoutResponse without a continuation: \(error)")
108+
}
109+
}
110+
context.isReadyToSendWriteWithoutResponse.send()
111+
}
112+
100113
func peripheral(
101114
_ cbPeripheral: CBPeripheral,
102115
didUpdateNotificationStateFor characteristic: CBCharacteristic,

0 commit comments

Comments
 (0)