iOS 18 didFinishLaunchingWithOptions called during prewaming

Hi,

I have seen a lot of answers in different threads Thread 1, Thread 2 that if an app doesn't support scenes, didFinishLaunchingWithOptions shouldn't be called during prewarming. Additionally, DTS Engineer on this Thread 3 mentioned that starting from late iOS 15, didFinishLaunchingWithOptions definitely won't be called during prewarming.

However, we are still seeing in our logs, that didFinishLaunchingWithOptions is called during prewarming, for some users, even on iOS 18. Our app doesn't support scenes, and our AppDelegate is completely Objective-C.

The problem is that in didFinishLaunchingWithOptions we set up our CoreData and Networking stack then we make a network request to fetch data and store it in database. When the app is launching in a prewarming state, CoreData stack can't load the persistent stores because the protected data isn't accessible during prewarming. We are setting the FileProtectionType to completeUnlessOpen for NSPersistentStoreDescription, which means the Core Data files are considered protected data. When user eventually launches app, network request resumes and crashes when trying to store data in database because Persistent Stores not loaded.

What would you recommend to resolve this issue? Rewriting the Core Data stack for lazy initialization is quite challenging since we're using an old library (RestKit) for networking and Core Data caching.

Thanks.

I have seen a lot of answers in different threads Thread 1, Thread 2 that if an app doesn't support scenes, didFinishLaunchingWithOptions shouldn't be called during prewarming.

To briefly clarify the issue there, when prewarm's usage was expanded*, it turned out that there were a very large number of apps doing VERY strange things at library load time. For example, initialize complex network stacks and even initializing WKWebView... all before main() has been called. THAT activity is what then prevented the app from suspending, which then lead to didFinishLaunchingWithOptions being called.

*Prewarming was actually occurring well before iOS 15. What changed is iOS 15 is that we started using it more broadly, which then exposed the issues above.

Additionally, DTS Engineer on this Thread 3 mentioned that starting from late iOS 15, didFinishLaunchingWithOptions definitely won't be called during prewarming.

Ironically, one of my coworker's pointed me at your post, which then lead me to look at Thread 3 again, and so now my latest response on thread 3 at least partially addresses what you're describing. You should take a look at that thread, however, I'll summarize some of what I covered there and then move on to the larger issue.

Starting here:

However, we are still seeing in our logs, that didFinishLaunchingWithOptions is called during prewarming, for some users, even on iOS 18.

Thread 3 lays this out in more detail, but what is likely going on is something like this:

  1. Prewarm launched your app at some point in the past and then suspended it. Note that didFinishLaunchingWithOptions was NOT called at this point.

  2. Some other API that provides some kind of background service/update noticed your app existed and woke it up to do "work". That then caused didFinishLaunchingWithOptions to occur and the normal launch cycle to proceed.

What would you recommend to resolve this issue? Rewriting the Core Data stack for lazy initialization is quite challenging since we're using an old library (RestKit) for networking and Core Data caching.

Off the top of my head, I can think of a few different options:

  • "Fix" the underlying problem. I'm including this for completeness, but I understand this may not be nice/viable given the state of your app.

  • Change the protection state to afterFirstUnlock. You may have good reasons for using completeUnlessOpen, but this is the simplest fix.

  • Move your entire initialization process "later" so that you can guarantee you're actually in the foreground. In practice, this generally means creating some kind of splash screen/placeholder that your app launches "into" then delaying ALL other app initialization until you're able to successfully initialize your CoreData stack. Effectively, you basically move "everything" you're doing in didFinishLaunchingWithOptions to that later point when you "know" you're in the foreground. How well this ends up working... depends a lot on what your app actually does. In the best case, your app is really well organized and you've basically just made the early launch process slightly faster and the late process slightly slower. The performance is basically the same and the problem goes away. Worst case... your initialization and interface are so tightly coupled that you just can't make it work.

  • This is outside what we normally recommend, but the other option is to detect that you're in this state and call "exit()", forcing your app to terminate.

Expanding on that last option (as it's the trickiest), how this well effect your larger app depends a lot on what you're actually doing. Strictly speaking, "classic" state restoration uses exit() to solve the same kind sort of issue you're having. The state restoration data was stored at "complete", so when the app was launched for a specific set of background delegates (this was a simpler time...) it would skip state restoration, perform the callback, and finally call exit(). The app could then restore state as usual if/when the app enters the foreground.

Of course, the problem with this approach is that if you're wrong about what state your app is in, then you'll effectively crash "yourself" (from the user perspective). Even worse, if things go REALLY wrong it's entirely possible for you to have a widespread issue that you can't really "see", since exit() won't generate any crash logs. You could mitigate that risk by calling "abort()", but that also means you'll be generating a lot of crash log "noise" you'll need to filter out.

Long story short, I’m not sure using exit like this is something I’d really recommend, but there is an argument that invisibly calling exit is better than leaving your app in a broken state.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

iOS 18 didFinishLaunchingWithOptions called during prewaming
 
 
Q