I reached for the new @concurrent attribute for the first time just now, and I want to verify that my understanding is correct and that I'm not missing a different recommended way to handle my situation.
I'm writing a method in a SwiftUI view type (MainActor-isolated by default), and it will perform some potentially lengthy synchronous work. I want the method to first update some UI state that indicates that the work is in progress, so I'll need to be on the main actor in the beginning, but then I want to offload the expensive work so as to not tie up the main actor, and lastly return to the main actor to reset the UI state.
My crude instinct was to wrap the synchronous work in a Task so as to free up the main actor, but then I wondered what the standard way would be to remain within structured concurrency for such a normal situation. Here's what I did (simplified a bit):
struct MyView: View {
@State var exportState: ExportState? = nil
enum ExportState {
case exporting
case exported(URL)
}
var body: some View { ... }
// @MainActor <- implicit, due to conformance to View
func exportData() async {
exportState = .exporting
let urlOfZip = await zipUserData()
exportState = .exported(urlOfZip)
}
@concurrent func zipUserData() async {
// .. expensive synchronous work, no suspension points
}
}
The main thing that feels a little weird is marking zipUserData as async when in reality it has no suspension points.
My two question are:
- Does this in fact achieve my goals? Have I missed something about how
@concurrentworks? - Is there a different way to handle this situation that is better for some reason?