1
1
// Copyright (c) 2021 Manuel Fernandez-Peix Perez. All rights reserved.
2
2
3
3
import Foundation
4
- import CoreBluetooth
4
+ @ preconcurrency import CoreBluetooth
5
5
import Combine
6
6
import os. log
7
7
8
8
/// An object that scans for, discovers, connects to, and manages peripherals using concurrency.
9
- public class CentralManager {
9
+ public final class CentralManager : Sendable {
10
10
11
11
private typealias Utils = CentralManagerUtils
12
12
@@ -27,12 +27,16 @@ public class CentralManager {
27
27
}
28
28
29
29
public var isScanning : Bool {
30
- self . context. isScanning
30
+ get async {
31
+ await self . context. isScanning
32
+ }
31
33
}
32
34
33
- public lazy var eventPublisher : AnyPublisher < CentralManagerEvent , Never > = {
34
- self . context. eventSubject. eraseToAnyPublisher ( )
35
- } ( )
35
+ public var eventPublisher : AnyPublisher < CentralManagerEvent , Never > {
36
+ get async {
37
+ await self . context. eventSubject. eraseToAnyPublisher ( )
38
+ }
39
+ }
36
40
37
41
private let cbCentralManager : CBCentralManager
38
42
private let context : CentralManagerContext
@@ -58,7 +62,7 @@ public class CentralManager {
58
62
try await self . context. waitUntilReadyExecutor. enqueue { [ weak self] in
59
63
// Note we need to check again here in case the Bluetooth state was updated after we last
60
64
// checked but before the work was enqueued. Otherwise we could wait indefinitely.
61
- guard let self = self , let isBluetoothReadyResult = Utils . isBluetoothReady ( self . bluetoothState ) else {
65
+ guard let self = self , let isBluetoothReadyResult = Utils . isBluetoothReady ( self . cbCentralManager . state ) else {
62
66
return
63
67
}
64
68
Task {
@@ -79,7 +83,7 @@ public class CentralManager {
79
83
/// Scans for peripherals that are advertising services.
80
84
public func scanForPeripherals(
81
85
withServices serviceUUIDs: [ CBUUID ] ? ,
82
- options: [ String : Any ] ? = nil
86
+ options: [ String : any Sendable ] ? = nil
83
87
) async throws -> AsyncStream < ScanData > {
84
88
try await withCheckedThrowingContinuation { continuation in
85
89
Task {
@@ -123,7 +127,7 @@ public class CentralManager {
123
127
}
124
128
125
129
/// Establishes a local connection to a peripheral.
126
- public func connect( _ peripheral: Peripheral , options: [ String : Any ] ? = nil ) async throws {
130
+ public func connect( _ peripheral: Peripheral , options: [ String : any Sendable ] ? = nil ) async throws {
127
131
guard await !self . context. connectToPeripheralExecutor. hasWorkForKey ( peripheral. identifier) else {
128
132
Self . logger. error ( " Unable to connect to \( peripheral. identifier) because a connection attempt is already in progress " )
129
133
@@ -181,7 +185,7 @@ public class CentralManager {
181
185
/// Cancels all pending operations, stops scanning and awaiting for any responses.
182
186
/// - Note: Operation for Peripherals will not be cancelled. To do that, call `cancelAllOperations()` on the `Peripheral`.
183
187
public func cancelAllOperations( ) async throws {
184
- if isScanning {
188
+ if await isScanning {
185
189
await self . stopScan ( )
186
190
}
187
191
try await self. context. flush ( error: BluetoothError . operationCancelled)
@@ -237,9 +241,7 @@ extension CentralManager.DelegateWrapper: CBCentralManagerDelegate {
237
241
238
242
func centralManagerDidUpdateState( _ central: CBCentralManager ) {
239
243
Task {
240
- defer {
241
- self . context. eventSubject. send ( . didUpdateState( state: central. state) )
242
- }
244
+ await self . context. eventSubject. send ( . didUpdateState( state: central. state) )
243
245
244
246
guard let isBluetoothReadyResult = Utils . isBluetoothReady ( central. state) else { return }
245
247
@@ -248,7 +250,9 @@ extension CentralManager.DelegateWrapper: CBCentralManagerDelegate {
248
250
}
249
251
250
252
func centralManager( _ central: CBCentralManager , willRestoreState dict: [ String : Any ] ) {
251
- self . context. eventSubject. send ( . willRestoreState( state: dict) )
253
+ Task {
254
+ await self . context. eventSubject. send ( . willRestoreState( state: dict) )
255
+ }
252
256
}
253
257
254
258
func centralManager(
@@ -265,7 +269,8 @@ extension CentralManager.DelegateWrapper: CBCentralManagerDelegate {
265
269
266
270
Task {
267
271
guard let continuation = await self . context. scanForPeripheralsContext. continuation else {
268
- Self . logger. info ( " Ignoring peripheral ' \( scanData. peripheral. name ?? " unknown " , privacy: . private) ' because the central manager is not scanning " )
272
+ let peripherlName = scanData. peripheral. name ?? " unknown "
273
+ Self . logger. info ( " Ignoring peripheral ' \( peripherlName, privacy: . private) ' because the central manager is not scanning " )
269
274
return
270
275
}
271
276
continuation. yield ( scanData)
@@ -286,7 +291,7 @@ extension CentralManager.DelegateWrapper: CBCentralManagerDelegate {
286
291
Self . logger. info ( " Received onDidConnect without a continuation " )
287
292
}
288
293
289
- self . context. eventSubject. send (
294
+ await self . context. eventSubject. send (
290
295
. didConnectPeripheral( peripheral: Peripheral ( peripheral) )
291
296
)
292
297
}
@@ -309,12 +314,14 @@ extension CentralManager.DelegateWrapper: CBCentralManagerDelegate {
309
314
break
310
315
}
311
316
312
- self . context. eventSubject. send (
313
- . connectionEventDidOccur(
314
- connectionEvent: event,
315
- peripheral: peripheral
317
+ Task {
318
+ await self . context. eventSubject. send (
319
+ . connectionEventDidOccur(
320
+ connectionEvent: event,
321
+ peripheral: peripheral
322
+ )
316
323
)
317
- )
324
+ }
318
325
}
319
326
#endif
320
327
@@ -356,7 +363,7 @@ extension CentralManager.DelegateWrapper: CBCentralManagerDelegate {
356
363
Self . logger. info ( " Disconnected from \( peripheral. identifier) without a continuation " )
357
364
}
358
365
359
- self . context. eventSubject. send (
366
+ await self . context. eventSubject. send (
360
367
. didDisconnectPeripheral( peripheral: Peripheral ( peripheral) , isReconnecting: isReconnecting, error: error)
361
368
)
362
369
}
@@ -378,7 +385,7 @@ extension CentralManager.DelegateWrapper: CBCentralManagerDelegate {
378
385
Self . logger. info ( " Disconnected from \( peripheral. identifier) without a continuation " )
379
386
}
380
387
381
- self . context. eventSubject. send (
388
+ await self . context. eventSubject. send (
382
389
. didDisconnectPeripheral( peripheral: Peripheral ( peripheral) , isReconnecting: false , error: error)
383
390
)
384
391
}
0 commit comments