I'm trying to determine why my UIKit application was launched, specifically whether it was launched by the user or by the system to execute a background task.
I recently added a BGProcessingTask to update data overnight. Consider this scenario:
- The user backgrounds the app at 10 AM.
- A background task is scheduled with
earliestBeginDateset to 4 AM the following day. - During those 18 hours, iOS terminates the app's process to reclaim memory.
- iOS later decides to execute the scheduled background task and launches a new app process in the background.
My understanding is that this results in a cold launch, including a call to application(_:didFinishLaunchingWithOptions:) and the initialization performed there.
This matters because some of our startup initialization triggers network requests that are unnecessary when the app is launched only to perform a background task.
Questions
1. Can iOS relaunch a previously terminated app to execute a scheduled BGProcessingTask?
I'm referring specifically to termination by the system (for example, memory pressure), not the user force-quitting the application.
2. Is there a reliable way to distinguish a foreground cold launch from a background task cold launch inside application(_:didFinishLaunchingWithOptions:)?
I tried checking UIApplication.shared.applicationState, but in my testing it was .background during didFinishLaunchingWithOptions in both scenarios, including when the user launched the app by tapping its icon.
Is there another documented API or launch context that reliably identifies why the process was started? I'm aware that iOS provides launch options for certain launch scenarios, but I'm specifically interested in distinguishing a background task-triggered cold launch from a user-initiated foreground cold launch.
3. If this distinction cannot be made reliably during didFinishLaunchingWithOptions, what is the earliest lifecycle callback where I can determine that the app is entering the foreground?
For example, should foreground-specific initialization be deferred until scene(_:willConnectTo:options:), sceneWillEnterForeground(_:), or another callback?
I'm specifically looking for a documented and reliable lifecycle signal, rather than heuristics or arbitrary delays.
The goal is to avoid unnecessary startup initialization and network requests when iOS launches the application solely to execute a background task.
Adding a few notes to expand on S. Beck's excellent reply:
- Can iOS relaunch a previously terminated app to execute a scheduled BGProcessingTask?
…
...not the user force-quitting the application.
It can do it for force quits as well, as described here:
- Is there a reliable way to distinguish a foreground cold launch from a background task cold launch inside application(_:didFinishLaunchingWithOptions:)?
Not in application(_:didFinishLaunchingWithOptions:). Not anymore!
...or for a pretty long while. Even before we introduced scenes, the system had "accumulated" a significant number of cases which would cause the app to launch directly into the background. Many of them weren't really documented, which meant trying to anticipate/avoid them wasn't really possible.
- If this distinction cannot be made reliably during didFinishLaunchingWithOptions, what is the earliest lifecycle callback where I can determine that the app is entering the foreground?
The advice above is excellent, but there was an additional point I wanted to make that's tied to this idea:
This matters because some of our startup initialization triggers network requests that are unnecessary when the app is launched only to perform a background task.
The basic problem here is that once you choose to skip this kind of work, the next question immediately becomes when you're going to do the work you just skipped. You may have been launched into the background for a processing task, but there's NO guarantee that you'll be terminated again, which means the next time your app wakes up is very likely to be when it's entering the foreground.
In other words, putting the work in didFinishLaunchingWithOptions just means you're juggling the work in two places instead of just one. Finally, on S. Beck's comment here:
It runs on every return to the foreground, not only the first, so guard one-time work with a flag:
VERY little network work is in fact "one-time". Your app could be suspended for HOURS at a time, and I have a hard time thinking of network work that looks like this:
- I'd WANT to do the work again, even though my app was running mere seconds ago (...because the user suspended and then relaunched my app).
BUT
- I WOULDN'T want to do the work again, even though it's been hours or days since my app was last active (...because my app was suspended in the background for a long time).
The upshot of all this is that you often end up putting this kind of code in willEnterForeground, so you're better off just relying on it alone.
Also, as an aside, these kinds of issues are a big part of why launch options went away:
The launch options would be the obvious place, but they are gone for good. ... And, every launch options key is now deprecated.
That is, the dynamics of app launching and suspension meant that every daemon that triggered a launch ended up needing code to:
-
Set the launch option if it happened to be launching the app.
-
A separate mechanism to deliver "whatever" it needed to pass to the app if the app was already running.
This worked ok when we didn't do much background launching, and lack of memory meant that apps tended to die frequently. It didn't really work at all once background launches proliferated and increasing memory meant we kept more apps suspended. For many apps, it’s entirely possible that they NEVER used launch options, simply because their apps’ usage patterns meant that they were never hitting the "right" launch paths.
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware