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"