This is my newbie implementation for beginBackgroundTask
@MainActor
class BackgroundTaskManager {
var backgroundTaskID: UIBackgroundTaskIdentifier = .invalid
func startBackgroundTask() {
backgroundTaskID = UIApplication.shared.beginBackgroundTask {
print("Background task about to expire with \(UIApplication.shared.backgroundTimeRemaining)")
UIApplication.shared.endBackgroundTask(self.backgroundTaskID)
}
print("Started background task with \(UIApplication.shared.backgroundTimeRemaining)")
}
func endBackgroundTask() {
UIApplication.shared.endBackgroundTask(backgroundTaskID)
}
}
When I search online, I see that apple has commented on their forum that these methods in fact does not need to be called from the main thread. But if I were to remove @MainActor and just make this class an actor instead, I get these warnings:
Main actor-isolated class property 'shared' can not be referenced on a nonisolated actor instance; this is an error in the Swift 6 language mode
Actor-isolated property 'backgroundTaskID' can not be referenced from the main actor; this is an error in the Swift 6 language mode
Ideally, I dont want to isolate to main, as that's an extra dependencies for my background jobs.
Some of APIs in UIKit and related parts are questionable at the moment (another example is UIDevice which got a few topics here as well). And this is really a bit of a complication here to just call an API from the background. I think over time this will get an improvement, but right now you can use a workaround.
It's just a difficulties bridging old (largely ObjC still I guess) systems. I don't think it is anything to blame at all, just a transition case.
I see that apple has commented on their forum that these
methods in fact does not need to be called from the main
thread.
It’s more official than that. In the Objective-C header these methods are decorated with NS_SWIFT_NONISOLATED, which is why you’re able to call them from non-main-actor contexts.
vns wrote:
You can make both methods async and get instance of the UIApplication:
That’ll work, but it requires bouncing to the main thread, which undermines the purpose of making this API thread safe.
I kinda like this approach:
actor BackgroundTaskManager {
@MainActor
init() {
self.app = UIApplication.shared
}
let app: UIApplication
func runJob(name: String) {
let t = self.app.beginBackgroundTask(withName: name) {
… handle expiration …
}
defer {
if t != .invalid {
self.app.endBackgroundTask(t)
}
}
… do something …
}
}
Of course the expiry handler is main actor isolated, courtesy of the NS_SWIFT_UI_ACTOR decoration in the header, which is another tricky complication )-:
Honestly, I’m not a fan of these APIs [1] and I encourage you to file an enhancement request for a better option.
Please post your bug number, just for the record.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple
[1] Although I’m even less of a fan of the ProcessInfo API that app extensions have to use. I talk about this in UIApplication Background Task Notes.