Hi all,
I'm running into a Swift Concurrency issue and would appreciate some help understanding what's going on.
I have a protocol and an actor set up like this:
protocol PersistenceListener: AnyObject { func persistenceDidUpdate(key: String, newValue: Any?) } actor Persistence { func addListener(_ listener: PersistenceListener) { listeners.add(listener) } /// Removes a listener. func removeListener(_ listener: PersistenceListener) { listeners.remove(listener) } // MARK: - Private Properties private var listeners = NSHashTable<AnyObject>.weakObjects() // MARK: - Private Methods /// Notifies all registered listeners on the main actor. private func notifyListeners(key: String, value: Any?) async { let currentListeners = listeners.allObjects.compactMap { $0 as? PersistenceListener } for listener in currentListeners { await MainActor.run { listener.persistenceDidUpdate(key: key, newValue: value) } } } }
When I compile this code, I get a concurrency error:
"Sending 'listener' risks causing data races"
Right. That’s because:
-
You got
listener
from one context, that of thenotifyListeners(…)
async function. -
You’re passing it to another context, the main actor.
-
And
listener
is not sendable.
How you fix this depends on how you want the code to behave. The two most common choices are:
-
If you don’t want to constrain the context in which listeners are called, force
PersistenceListener
to be sendable. -
If you always want the listeners to be called on the main actor, decorate everything with
@MainActor
.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"