Hi,
We are running into issues with iOS app prewarming, where the system launches our app before the user has entered their passcode.
In our case, the app stores flags, counters, and session data in UserDefaults and the Keychain. During prewarm launches:
- UserDefaults only returns default values (nil, 0, false). We have no way of knowing whether this information is valid or just a placeholder caused by prewarming.
- Keychain items with
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
are inaccessible, which can lead to broken business logic (the app can assume no session exists). - No special launch options or environment variables appear to be set.
We can reproduce this 100% of the time by starting a Live Activity in the app before reboot.
Here’s an example of the workaround we tried, following older recommendations:
__attribute__((constructor))
static void ModuleInitializer(void) {
char* isPrewarm = getenv("ActivePrewarm");
if (isPrewarm != NULL && isPrewarm[0] == '1') {
exit(0); // prevent prewarm launch from proceeding
}
}
On iOS 16+, the ActivePrewarm
environment variable doesn’t seem to exist anymore (though older docs and SDKs such as Sentry reference it).
We also tried listening for UIApplication.protectedDataDidBecomeAvailableNotification,
but this is not specific to prewarming (it also fires when the device gets unlocked) and can cause watchdog termination if we delay work too long.
Questions:
- Is there a supported way to opt out of app prewarming?
- What is the correct way to detect when an app is being prewarmed?
- Is the
ActivePrewarm
environment variable still supported in iOS 16+?
Ideally, the UserDefaults API itself should indicate whether it is returning valid stored values or defaults due to the app being launched in a prewarm session.
We understand opting out may impact performance, but data security and integrity are our priority. Any guidance would be greatly appreciated.
So, let me start by sorting out prewarming:
We are running into issues with iOS app prewarming, where the system launches our app before the user has entered their passcode.
Prewarming is not why your app is waking up. This forum post goes into this in more detail, however, quoting myself for the most relevant detail:
"Since late iOS 15, the Prewarm process suspends your app very early in the dyld load process, specifically before ANY 3rd party library (and most of our libraries) can be loaded. By design, your application cannot have ANY effect on that process. Indeed, it's entirely possible for prewarm to have created your process minutes or even hours before ANY of your app’s code actually executed. Prewarming is only what created your app’s process, NOT the reason why your app is actually executing code."
That leads to here:
On iOS 16+, the ActivePrewarm environment variable doesn’t seem to exist anymore (though older docs and SDKs such as Sentry reference it).
Are you sure ModuleInitializer is running when you "think" it (pre-main)? Duet still sets "ActivePrewarm" and UIApplication then clears it, which is how it basically "always" worked. If it's not "there", then you're either running code later than you think you are or you're not being prewarmed.
All that leads to here:
We can reproduce this 100% of the time by starting a Live Activity in the app before reboot.
That Live Activity is why your app is running prior to first unlock. It's possible that prewarm had a limited role*, but the larger issue is that Live Activity is running your app.
*In general, most of our background services don't really differentiate between these two states:
-
The app is suspended in the background.
-
The app is not running at all.
That is, if it's "worth" waking your app to run code in the background, then it's "worth" launching your app into the background as well. However, I have seen edge cases (IMHO, bugs) where the background service WOULD wake a suspended process, but would NOT launch it. In that case, prewarm can be a factor, since it creates the process which the background service then wakes. However, the bug here is in the service that's doing the waking, NOT prewarm.
- Is there a supported way to opt out of app prewarming?
No, however, you're also asking the wrong question. The issue here isn't whether or not your app was prewarmed, it's that you're executing code prior to first unlock and you don't want "that".
- What is the correct way to detect when an app is being prewarmed?
Changing that question to "how do I detect that I'm running prior to first unlock", the answer is actually here:
Keychain items with kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly are inaccessible,
The keychain actually returns errSecInteractionNotAllowed* for "the key exists but you can't have it" which, in the configuration above, would mean you're running prior to first unlock. More importantly, you're now asking the question that matters ("can I access the data I need") and acting on that, instead of trying to infer the device state. What matters here is that you can't access the data your app needs, not WHY you can't access it.
*Thanks Quinn, I hate tracking down keychain errors!
- Is the ActivePrewarm environment variable still supported in iOS 16+?
For completeness, yes, it does (see above for details).
__
Kevin Elliott
DTS Engineer, CoreOS/Hardware