I was reading through this blog, as I have a similar use-case: https://kelvas09.github.io/blog/posts/closure-delegate-to-async/
I noticed the following snippet:
actor BluetoothLEScanWrapper {
enum BluetoothLEScanError: Error {
case bluetoothNotAvailable
}
private let bluetoothLEDelegate: BluetoothLEDelegate = BluetoothLEDelegate()
private var activeTask: Task<[BluetoothLEDevice], Error>?
func scan(for seconds: Double = 3.0) async throws -> [BluetoothLEDevice] {
if let existingTask = activeTask {
return try await existingTask.value
}
let task = Task<[BluetoothLEDevice], Error> {
guard bluetoothLEDelegate.bluetoothIsOn else {
activeTask = nil
throw BluetoothLEScanError.bluetoothNotAvailable
}
self.bluetoothLEDelegate.central.scanForPeripherals(withServices: nil)
try await Task.sleep(nanoseconds: (UInt64(seconds) * 1_000_000_000))
let devices = bluetoothLEDelegate.foundPeripheral.compactMap { BluetoothLEDevice(peripheral: $0) }
bluetoothLEDelegate.central.stopScan()
bluetoothLEDelegate.foundPeripheral = []
activeTask = nil
return devices
}
activeTask = task
return try await task.value
}
...
The author specifically calls attention to the use of actor. As I understand it, this is to avoid data races with activeTask
in case scan()
is called repeatedly. Is my understanding correct? Thanks for the help.
Yes, you are correct. Actors are used to prevent data races. See WWDC videos Protect mutable state with Swift actors and Eliminate data races using Swift Concurrency. Or see The Swift Programming Language: Concurrency: Actors.
The scan
method will be isolated to the actor, as will the Task
it creates.