I'm struggling to execute code when app goes to background using new async/await concurrency model in Swift. Here my code:
@main
struct TestApp: App {
@Environment(\.scenePhase) var scenePhase
var body: some View {
ContentView()
}
.onChange(of: scenePhase) {
if $0 == .background {
Task {
var bgTaskId: UIBackgroundTaskIdentifier = .invalid
bgTaskId = UIApplication.shared.beginBackgroundTask {
print("ran out of time")
UIApplication.shared.endBackgroundTask(bgTaskId)
bgTaskId = .invalid
}
await UserManager.user.save() // func save() async {...}
print("executes this immediately")
UIApplication.shared.endBackgroundTask(bgTaskId)
bgTaskId = .invalid
}
}
}
}
struct User {
func save() async {
print("Never calls this...")
await Task.sleep(nanoseconds: 3_000_000_000)
}
}
So I never see the print call in save()
but I do see all the print calls in the Task.init
of onChange()
. Is there something about how async/await executes that makes this not possible or am I missing something here?
I am able to execute background fetches the pre-async/await way with a DispatchQueue.global().async{...}
and subsequent calls to .beingBackgroundTask
and endBackgroundTask
within the onChange()
call so I know it's something about async/await
specifically. Thanks!
CodePudding user response:
This pattern works fine. But the print
statements make me suspect that you must be attempting to watch this in the Xcode debugger console. But when testing background lifecycle events, you do not want to be attached to the Xcode debugger, as that artificially changes the app lifecycle.
That begs the question as to how one would watch for logging messages while not connected to Xcode. Personally, I use the unified logging system, namely
A few unrelated observations:
My code snippet uses
guard
early exits, to avoid towers of braces.I consciously avoid starting the background task in an asynchronous process. We want to avoid races between the app suspending and when the background task is created. So I create the background task before launching the asynchronous process.
I always make sure that the background task identifier is not
.invalid
already before trying to end the task. You want to avoid races between the timeout and the graceful termination.It's probably prudent to supply a name for your background task.
None of these four points are terribly critical to the question at hand, but I wanted to simply demonstrate some best practices: The key observation is to (a) test outside of Xcode; and (b) use unified logging system so that you can monitor the progress. There are other approaches to monitor the progress, but this is probably easiest.