What are the reasons for an application to be launched from the background?

Our application has seen a surge in the volume of background launches starting from April and May, and we want to know under what circumstances the application can be launched from the background.

First, here's how I determined background launches: we analyze user logs and append UIApplication.appState to each line of log, finding that every log from the start to the end of user sessions has an appState of UIApplicationStateBackground.

By checking the "ActivePrewarm" in main() and printing the launch options from application:didFinishLaunchingWithOptions:, we found several scenarios for background launches:

  1. launchOptions has a value with the key UIApplicationLaunchOptionsRemoteNotificationKey.
  2. launchOptions has no value and there is no "ActivePrewarm."
  3. launchOptions has no value but has "ActivePrewarm."

I would like to know:

  1. Under what circumstances will notifications trigger a background launch (I cannot replicate this locally)?
  2. Under what circumstances does an application launch in the background and trigger application:didFinishLaunchingWithOptions: but without any launch options?

I hope informations below can provide some insights.

Regarding "ActivePrewarm," I've read various questions and answers in the Apple Developer Forums, such as this thread, which states that "ActivePrewarm" does not trigger application:didFinishLaunchingWithOptions: but occurs due to certain behaviors in the application. I would like to know what behaviors may cause this background launch, as there is no information in the launch options, or how I can identify what behaviors triggered it.

Specifically, based on that same thread, I've tried to gather more information using runningboardd, and I've currently identified two special cases:

  1. When I restart my phone and unlock it after a short period, there is information:
	<RBSDomainAttribute| domain:"com.apple.dasd" name:"DYLDLaunch" sourceEnvironment:"(null)">
	]>
  1. Every day, at intervals of a few hours, there is information:
	<RBSDomainAttribute| domain:"com.apple.dasd" name:"DYLDLaunch" sourceEnvironment:"(null)">
	]>

Then, the following similar information follows:

12:15:56.047625+0800	runningboardd	Executing launch request for app<{my_bundle_id}((null))> (DAS Prewarm launch)
12:15:56.050311+0800	runningboardd	Creating and launching job for: app<{my_bundle_id}((null))>
12:15:56.050333+0800	runningboardd	_mutateContextIfNeeded called for {my_bundle_id}
12:15:56.080560+0800	runningboardd	app<{my_bundle_id}((null))>: -[RBPersonaManager personaForIdentity:context:personaUID:personaUniqueString:] required 0.000954 ms (wallclock); resolved to {1000, 39E408CF-2E67-4DB0-BF73-CFC5792285CD}
12:15:56.080632+0800	runningboardd	'app<{my_bundle_id}(39E408CF-2E67-4DB0-BF73-CFC5792285CD)>' Skipping container path lookup because containerization was prevented (<RBSLaunchContext: 0xcd8cc9180>)
12:15:56.080939+0800	runningboardd	'app<{my_bundle_id}(39E408CF-2E67-4DB0-BF73-CFC5792285CD)>' Constructed job description:
<dictionary: 0xcd8aa2a00> { count = 19, transaction: 0, voucher = 0x0, contents = *** }
12:15:56.084839+0800	runningboardd	[app<{my_bundle_id}((null))>:1649] Memory Limits: active 4096 inactive 4096
 <private>
12:15:56.084861+0800	runningboardd	[app<{my_bundle_id}((null))>:1649] This process will be managed.
12:15:56.084882+0800	runningboardd	Now tracking process: [app<{my_bundle_id}((null))>:1649]
12:15:56.084928+0800	runningboardd	Calculated state for app<{my_bundle_id}((null))>: running-active (role: Background) (endowments: (null))
12:15:56.086762+0800	runningboardd	Using default underlying assertion for app: [app<{my_bundle_id}((null))>:1649]
12:15:56.086977+0800	runningboardd	Acquiring assertion targeting [app<{my_bundle_id}((null))>:1649] from originator [app<{my_bundle_id}((null))>:1649] with description <RBSAssertionDescriptor| "RB Underlying Assertion" ID:33-33-23101 target:1649 attributes:[
	<RBSDomainAttribute| domain:"com.apple.underlying" name:"defaultUnderlyingAppAssertion" sourceEnvironment:"(null)">,
	<RBSAcquisitionCompletionAttribute| policy:AfterApplication>
	]>
12:15:56.087203+0800	runningboardd	Assertion 33-33-23101 (target:[app<{my_bundle_id}((null))>:1649]) will be created as active
12:15:56.087946+0800	runningboardd	[app<{my_bundle_id}((null))>:1649] reported to RB as running
12:15:56.088053+0800	runningboardd	Calculated state for app<{my_bundle_id}((null))>: running-active (role: Background) (endowments: (null))
12:15:56.088114+0800	runningboardd	[app<{my_bundle_id}((null))>:1649] Set jetsam priority to 0 [0] flag[1]
12:15:56.088136+0800	runningboardd	[app<{my_bundle_id}((null))>:1649] Resuming task.
12:15:56.088211+0800	runningboardd	[app<{my_bundle_id}((null))>:1649] Set darwin role to: Background
12:15:56.088449+0800	runningboardd	[app<{my_bundle_id}((null))>:1649] set Memory Limits to Hard Inactive (4096)
12:15:56.089314+0800	runningboardd	Successfully acquired underlying assertion for [app<{my_bundle_id}((null))>:1649]
12:15:56.589755+0800	runningboardd	Invalidating assertion 33-76-23100 (target:app<{my_bundle_id}((null))>) from originator [osservice<com.apple.dasd>:76]
12:15:56.590332+0800	runningboardd	Removed last relative-start-date-defining assertion for process app<{my_bundle_id}((null))>
12:15:56.593760+0800	runningboardd	[app<{my_bundle_id}((null))>:1649] Suspending task.
12:15:56.594120+0800	runningboardd	Calculated state for app<{my_bundle_id}((null))>: running-suspended (role: None) (endowments: (null))

From these logs, I understand that the system is accelerating the launch speed of the application.

But the time interval between these two logs below is very short, which suggests that the prewarm is executed just before main, and then the process is suspended. Is this understanding correct?

12:15:56.089314+0800 runningboardd Successfully acquired underlying assertion ...
12:15:56.589755+0800 runningboardd Invalidating assertion ...

Regarding "DAS DYLD3 Closure Generation," I speculate that after a user restarts their phone, the system uses DYLD3 to prepare closures for frequently used applications, allowing for faster application launches. Is this assumption correct?

Our application has seen a surge in the volume of background launches starting from April and May, and we want to know under what circumstances the application can be launched from the background.

Why is that an issue/problem? One thing to understand here is that, as far as the system is concerned, background launches are a feature, not a bug. For example, "perfect" launch behavior on iOS would be that the system was ALWAYS able to fully launch every app BEFORE the user tried to open it. That obviously isn't possible, but the idea that background launches are something the system is trying to avoid is simply false.

I would like to know:

  • Under what circumstances will notifications trigger a background launch (I cannot replicate this locally)?
  • Under what circumstances does an application launch in the background and trigger application:didFinishLaunchingWithOptions: but without any launch options?

I hope informations below can provide some insights.

The basic assumption underneath these questions is that the system has a specific, centralized "list" of every possible circumstance that will trigger a background launch and that this list can be easily produced. Unfortunately, that's not really how the system works today. While that list was somewhat possible in iOS 4 (when background app were introduced) the systems overall complexity has VASTLY increased since then without us having made any particular effort to actively track this kind of behavior in a centralized way.

The other assumption here is that understanding "why" will be useful when, in my experience, that isn't actually the case. Fundamentally, your app has very little control over when it's launched and, in the cases it does, preventing any particular launch case will mean giving up some other useful functionality. More to the point, there's no guarantee that your app won't find itself in exactly the same situation again, forcing you to repeat the same investigation process all over again. The better option is to adapt your app to handle background launches gracefully, at which point the entire problem goes away.

But the time interval between these two logs below is very short, which suggests that the prewarm is executed just before main, and then the process is suspended. Is this understanding correct?

Yes, though I want to be very precise about this point:

executed just before main, and then the process is suspended

What actually happens here is that the system launches your app, creates your process, and then dyld starts loading your apps libraries. The first library it loads is libsystem (this behavior is hard coded into dyld's implementation) and libsystem then suspends your app.

The critical point here is that this sequence was specifically created to ensure that there wasn't ANY way that a prewarm launch could, on it's own, cause a "full" app start (that is, your app entering main() and then continuing into the normal app launch sequence). Since late iOS 15, prewarm may create your process but it is NEVER the reason any of your code is executing.

Regarding "DAS DYLD3 Closure Generation," I speculate that after a user restarts their phone, the system uses DYLD3 to prepare closures for frequently used applications, allowing for faster application launches. Is this assumption correct?

Generally speaking, yes. I'm not sure we need to rebuild the cache after every boot but aside from that this is correct.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

The issue is not with the background launch itself, but rather that we would like clear documentation to understand what behaviors trigger a background launch.

For example, according to the documentation, silent notifications are supposed to launch the app in the background, but in actual scenarios, I have found that regular notifications can also trigger the background launch of the app.

Therefore, I hope there can be a list that provides at least some clarity on the situations in which the app can launch from the background, so that we know not only what happens but also why it happens.

For example, according to the documentation, silent notifications are supposed to launch the app in the background, but in actual scenarios, I have found that regular notifications can also trigger the background launch of the app.

How exactly are you determining that a notification is what triggered the launch? Is this through "UIApplicationLaunchOptionsRemoteNotificationKey" or something else? How is the payload for your push configured and, more specifically, are you including "content-available" on regular pushes? What priority are you using and does your app include a notification service extension?

Therefore, I hope there can be a list that provides at least some clarity on the situations in which the app can launch from the background, so that we know not only what happens but also why it happens.

I'm sorry, but what you're asking for simply does not exist. Every framework is responsible for it's own background API implementation and documentation, so there isn't a single, centralized description of how the system is supposed to work in every detail. In addition, the specific, detailed behaviors of each framework often involve undocumented details. Typically these are either side effects of the current implementation or holdovers from earlier implementations. Finally, the behavior of one framework can end up changing the final behavior of other frameworks by creating app states that would otherwise not exist if only ONE background API/framework was being used.

In any case, all these factors together are why there isn't any single document that answers a seemingly "basic" question like "why an app would launch into the background". Putting together the list at all would be quite difficult and, more importantly it's basically guaranteed that the document wouldn't actually cover every edge case or detail.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

How exactly are you determining that a notification is what triggered the launch? Is this through "UIApplicationLaunchOptionsRemoteNotificationKey" or something else? How is the payload for your push configured and, more specifically, are you including "content-available" on regular pushes? What priority are you using and does your app include a notification service extension?

We determine that this background launch was triggered by a remote notification via UIApplicationLaunchOptionsRemoteNotificationKey, the value corresponding to UIApplicationLaunchOptionsRemoteNotificationKey is almost identical to the other regular notifications, and we did not use 'content-available'. Our app does include a notification service extension.

We determine that this background launch was triggered by a remote notification via UIApplicationLaunchOptionsRemoteNotificationKey, the value corresponding to UIApplicationLaunchOptionsRemoteNotificationKey is almost identical to the other regular notifications, and we did not use 'content-available'. Our app does include a notification service extension.

Huh. Do you have logging about exactly what other delegate callbacks were made into your app, particularly "willPresentNotification" and "didReceiveNotificationResponse"?

Looking at our code, I'm not seeing any direct path that would cause a standard push to launch an app for and lead to UIApplicationLaunchOptionsRemoteNotificationKey without "content-available". However, and this is just a theory, it's possible that a one of the two above would trigger wake ("willPresentNotification") or a wake/launch ("didReceiveNotificationResponse") and that your app is then "picking up" the payload for that launch through UIApplicationLaunchOptionsRemoteNotificationKey as part of the launch process.

If you're able to reproduce the issue somewhat reliably and you want to have this investigated further, then the next steps would be to

  • Install the APNS profile on your test device.

  • Reproduce the issue multiple times, noting the EXACT time you performed each test.

  • Collect a sysdiagnose using instructions from the profile page.

  • File a bug on this and upload the sysdiagnose, the list of test times you created, as well as any other log data you have, particularly app log data that corresponds to the testing you did above.

Once that's done, post the bug number back here and I'll see what I can determine.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

What are the reasons for an application to be launched from the background?
 
 
Q