Unexpected order of scene delegate method calls in Swift iOS app

I am encountering an unusual sequence of scene delegate method calls in my Swift iOS app. Specifically, sceneWillEnterForeground gets called before sceneDidEnterBackground, but with a different UIWindowScene instance.

Here's the specific scenario:

Other apps are open and visible in the minimized state in iOS. I quickly minimize my app and instantly terminate it (swipe up to kill). In this case, I observe the following sequence of debug prints:

// app launch
sceneWillEnterForeground <UIWindowScene: 0x10270b110>...
sceneDidBecomeActive <UIWindowScene: 0x10270b110>...
// app termination
sceneWillResignActive <UIWindowScene: 0x10270b110>...
sceneDidDisconnect <UIWindowScene: 0x10270b110>...
sceneWillEnterForeground <UIWindowScene: 0x103e06510>...
sceneDidEnterBackground <UIWindowScene: 0x103e06510>...

Notice that sceneWillEnterForeground is called before sceneDidEnterBackground, but with a different UIWindowScene memory address.

However, if I kill the app without other apps visible in the minimized state, or if I do it slowly, I observe the standard sequence of calls without the additional sceneWillEnterForeground:

// app launch
sceneWillEnterForeground <UIWindowScene: 0x106007560>...
sceneDidBecomeActive <UIWindowScene: 0x106007560>...
// app termination
sceneWillResignActive <UIWindowScene: 0x106007560>...
sceneDidDisconnect <UIWindowScene: 0x106007560>...

Why does sceneWillEnterForeground with a new UIWindowScene memory address get called before sceneDidEnterBackground of the previous UIWindowScene memory address in the first scenario? Is this a standard behavior? Can the order of delegate methods vary depending on system behavior and do new UIWindowScene instances get created in such situations?

Any insight into this would be greatly appreciated.

Note that if we use AppDegate, we can encounter the same behavior where applicationWillEnterForeground is called before applicationDidEnterBackground.

Here is a SceneDelegate code so you could quickly reproduce it.

import UIKit

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let _ = (scene as? UIWindowScene) else { return }
    }

    func sceneDidDisconnect(_ scene: UIScene) {
        print("sceneDidDisconnect \(scene)")
    }

    func sceneDidBecomeActive(_ scene: UIScene) {
        print("sceneDidBecomeActive \(scene)")
    }

    func sceneWillResignActive(_ scene: UIScene) {
        print("sceneWillResignActive \(scene)")
    }

    func sceneWillEnterForeground(_ scene: UIScene) {
        print("sceneWillEnterForeground \(scene)")
    }

    func sceneDidEnterBackground(_ scene: UIScene) {
        print("sceneDidEnterBackground \(scene)")
    }
}

The order of delegate methods only applies to a specific scene. If there are multiple scenes, each scene may transition through its own states without regard for other scenes.

In your specific case, each scene progressed in a valid manner, and so everything is behaving as expected, relative to the scene life cycle at least. I don't know why a 2nd scene was created in your first case but its behavior (internal to itself, and relative to other scenes) is correct.

So the main question is why it creates the 2nd scene if you close the app quickly when there are other apps are in the minimized (background) state.

Maybe it's to take a snapshot of the app.

But it seems weird why the system doesn't do so if there is no other apps minimized. Also, it can lead to unexpected behavior if you have something that is triggered by sceneWillEnterForeground in your app.

For instance if you fetch some data from backend, the request will be triggered but no response will be received. In my case, if access token is expired, it lead to my app making a refresh token request, making the backend generate a new access and refresh tokens. The problem is before the response from backend is received, the app is terminated, and the tokens are not stored in the app. On the next launch, the app uses an old already invalid refresh token, receives 401 "Refresh token is not assigned to the user" response and the user gets logged out.

If using AppDelegate instead of SceneDelegate and quickly terminating the app, I get the following events:

11:42:33.615 applicationDidEnterBackground <UIApplication: 0x10930bc20>
11:42:33.965 applicationWillEnterForeground <UIApplication: 0x10930bc20>
11:42:33.988 applicationDidEnterBackground <UIApplication: 0x10930bc20>

I'm using RxSwift and AppDelegate in my app, so I've fixed it by combining applicationDidBecomeActive and applicationDidEnterBackground, and adding a distinctUntilChanged and a 1 second throttle to the events. This approach allows me to accurately determine whether the app is in the foreground, while disregarding the extraneous applicationWillEnterForeground event.

Unexpected order of scene delegate method calls in Swift iOS app
 
 
Q