iosswiftasync-awaitconcurrencyactor

Use of swift actor in delegate facade


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.


Solution

  • 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.