I've recently developed a networking package to implement a layer on top of URLSession to execute network requests.
While doing so, I wanted to make it easy for the users of this package to update their UI after receiving the result. (I was not so optimistic I could achieve this though) Like they don't have to specify their Task to run on main actor.
Then I tried annotating the networking request function with @MainActor
to make it run and return its result with main actor. Which was fine because the subsequent calls within would wait on other async calls that are not run on main actor. So the method signature was like:
@MainActor
func fetch() async -> String {
// some other async calls happen and I return String eventually
}
Then I tried to call this request method from a task as following:
Task {
// here we are on a background thread
let result = await networking.fetch()
// after receiving the result the `Task` would continue on the main thread
// and updating the UI via delegate would be fine, because the `fetch` method runs on Main actor
delegate.didCompleteFetching(value: result)
}
So this was working, the Task
would start on a background thread, and after receiving the result from networking with main actor, the Task
would remain on the main thread so I could update the UI, without specifying that Task
should run on MainActor
or anything. Although it felt weird, because I was expecting the Task to switch back to its original thread or a background thread, I thought maybe it is intended to behave this way, and it was solving my problem and I proceeded with this approach.
All of this implementation was done using Xcode 13.3 though.
Now fast forward to today, and when I try the same package with Xcode 14.0, I get crash due to attempting to update UI on a non main thread. So the behaviour of the Task is now different than how it was before, if we look at the code snippet again with Xcode 14.0:
Task {
// here we are on a background thread
let result = await networking.fetch()
// Now on Xcode 14.0, after receiving the result the `Task` will switch back to a background thread
//and attempting to UI via delegate will result in crash
delegate.didCompleteFetching(value: result)
}
Now the same code is crashing as I explained in comments in the code snippet above.
I see a few possibilities and some actions that I can take with regard to this:
1.) The behaviour I get on Xcode 13.3 was not intended, and although it helped me for what I needed, it was wrong in the first place, and now with Xcode 14.0 it is being fixed and I must update my code to specifically call networking method on a Task
that runs on main actor, and remove @MainActor
annotation from my networking methods.
2.) The behaviour I get on Xcode 13.3 was intended and correct, but now there is a problem with Xcode 14.0 and I should file a bug report, and keep my code as it is.
3.) Regardless of the behaviour of Task, I should update my code to remove @MainActor annotation from my networking method, and require callers to run on main actor if they need to update UI, because what I did in the first place was a misuse of the concurrency.
Also I created a small project here to better mimic a real scenario of a networking and UI updating:
Which will run fine on Xcode 13.3, 13.4 but will crash on Xcode 14.0