I'm converting my Swift app to use Combine as well as async/await
and I'm trying to understand what's the best way to handle interactions between asynchronous functions and the main thread.
Here's an asynchronous function that loads a user:
class AccountManager {
static func fetchOrLoadUser() async throws -> AppUser {
if let user = AppUser.current.value {
return user
}
let syncUser = try await loadUser()
let user = try AppUser(syncUser: syncUser)
AppUser.current.value = user // [warning]: "Publishing changes from background threads is not allowed"
return user
}
}
And a class:
class AppUser {
static var current = CurrentValueSubject<AppUser?,Never>(nil)
// ...
}
Note: I chose to use CurrentValueSubject
because it allows me to both (1) read this value synchronously whenever I need it and (2) subscribe for changes.
Now, on the line marked above I get the error Publishing changes from background threads is not allowed
, which I understand. I see different ways to solve this issue:
AccountManager
class as @MainActor
Since most of the work done in asynchronous functions is to wait for network results, I'm wondering if there is an issue with simply running everything on the main thread. Would that cause performance issues or not?
DispatchQueue.main.sync
Is that a reasonable solution, or would that cause threading problems like deadlocks?
DispatchGroup
with enter()
, leave()
and wait()
Like in this answer. Is there a difference at all with solution #2? Because this solution needs more lines of code so I'd rather not use it if possible —I prefer clean code.
You can wrap the call in an await MainActor.run { }
block. I think this is the most Swifty way of doing that.
You should not use the Dispatch mechanism while using Swift Concurrency, even though I think DispatchQueue.main.async { }
is safe to use here.
The @MainActor
attribute is safe but shouldn’t be used on an ObservableObject
and could potentially slow down the UI if CPU-bound code is run in a method of the annotated type.