For the first part of this thread, the goal was just more efficient code. The assumption is that this:
func outerWrapper<S>(arg1: Any, arg2: String = "ignore me") throws -> AsyncTask<S> {
return theAsyncFunc(arg1: arg1, arg2: arg2)
}
is more efficient than this:
func outerWrapper<S>(arg1: Any, arg2: String = "ignore me") async throws -> S {
return await theAsyncFunc(arg1: arg1, arg2: arg2)
}
Negligibly so, sure. But I actually care about the efficiency of my code. It also makes for fewer edits in the migration to concurrency. If the former is not allowed in swift, so be it. Not a big deal. Was just curious and wanted to advocate.
For the second side note, the overall Task
is run in a method of an ObservableObject, which serves as one of my view models and which is triggered by some user interaction with SwiftUI. The added complication is that the class inherits some functionality from NSObject and conforms to a protocol that is common to most of the models in my project. So:
class MyViewModel: NSObject, LoadingProtocol, SomeObjcDelegate {
@Published var loading: Bool = false
.....
func businessLogic() {
loading = true
Task {
do {
let result = try await someAsyncFunc(....)
await MainActor.run {
self.swiftUIStateVar = result.someValue
}
} catch {
await MainActor.run {
self.errorToDisplay = error
}
} finally {
await MainActor.run {
self.loading = false
}
}
}
}
}
And I agree that doing everything in the main actor is ideal, which is what I kind of assumed would be the case. And, if I'm not mistaken, I would be able to add the @MainActor
modifier to the class definition to make it so... except for the inheritance. And the modifier prevented the class from conforming to the protocol.
The await someAsyncFunc(....)
is ultimately async because it makes a URLSession dataTask call. At run time, when it returned, I got a purple warning/error about the code not running on the main actor, and the UI didn't update. As written above, it seems to work. But I posted just because I'm still new to the api and maybe there's a better way given the constraints of my situation.
And my critique, then, with my limited perspective, is that it should be main actor by default, and the declaration I would like to add would mark whatever can be pushed off to other actors: something like await method() on someClassOfActor
. Arguably, each method in the call stack from someAsyncFunc
down to just before the actual dataTask
is quick enough to run on the main actor. And every step processing the returned data back up the call stack is also probably quick enough. So that, at least in my case, main actor by default without extra decorators would be sufficient.
PromiseKit, the package from which I'm migrating, essentially operates this way, dispatching each callback on the main queue by default unless instructed otherwise.