Appendix A. Application Lifetime Events

The fundamental events that notify you of stages in the lifetime of your app as a whole, and give your code an opportunity to run in response, are extraordinarily important. This appendix is devoted to a survey of them, along with some typical scenarios in which they will arrive.

Application States

In the very early days of iOS — before iOS 4 — the lifetime of an app was extremely simple: either it was running or it wasn’t. The user tapped your app’s icon in the home screen, and your app was launched and began to run. The user used your app for a while. Eventually, the user clicked the Home button and your app was terminated — it was no longer running. The user had quit your app. Launch, run, quit: that was the entire life cycle of an app. If the user decided to use your app again, the whole cycle started over.

The reason for this simplicity was that, before iOS 4, an iOS device, with its slow processor and its almost brutal paucity of memory and other resources, compensated for its own shortcomings by a simple rule: it could run only one app at a time. While your app was running, it occupied not only the entire screen but the vast majority of the device’s resources, leaving room only for the system and some hidden built-in processes; it had, in effect, sole and complete control of the device.

Starting in iOS 4, that changed. Apple devised an ingenious architecture whereby, despite the device’s limited resources, more than one app could run simultaneously — sort of. The Home button changed its meaning and its effect upon your app. In iOS 4 and later, when the user clicks the Home button to leave your app, your app does not die; technically, the Home button does not terminate your app. Instead, when your app occupies the screen, it is in the foreground (or frontmost); then, when some other app proceeds to occupy the screen, your app is backgrounded and suspended.

Suspension means that your app is essentially freeze-dried; its process still exists, but it isn’t actively running, and it isn’t getting any events — though notifications can be stored by the system for later delivery in case your app comes to the front once again. And because it isn’t running, it isn’t using so much of the device’s precious resources. In particular, it’s not using the CPU, and some memory may be freed up by clearing the backing store of layers. Later, when the user returns to your app after having left it to use some other app for a while, your app is found in the very same state as when the user left it. The app was not terminated; it simply stopped and froze, and waited in suspended animation. Returning to your app no longer means that your app is launched, but merely that it is resumed.

All of this is not to say, however, that your app can’t be terminated. It can be — though not by the user clicking the Home button. For example, the user might switch off the device; that will certainly terminate your app. And a savvy user might force-terminate your app from the app switcher. The most common scenario, however, is that the system quietly kills your app while it is suspended. This undermines the app’s ability to resume; when the user returns to your app, it will have to launch from scratch, just as in the pre–iOS 4 days. The death of your app under these circumstances is rather like that of the scientists killed by HAL 9000 in 2001: A Space Odyssey — they went to sleep expecting to wake up later, but instead their life-support systems were turned off while they slept. The iOS system’s reasons for killing your app are not quite as paranoid as HAL’s, but they do have a certain Darwinian ruthlessness: your app, while suspended, continues to occupy a chunk of the device’s memory, and the system needs to reclaim that memory so some other app can use it.

After the user leaves your app, therefore, one of two things might happen later when the user returns to it. It could be woken and resumed from suspended animation, in the very state that it was in when the user left it, or it could be launched from scratch because it was terminated in the background. It is this bifurcation of your app’s possible fates that state saving and restoration, discussed at the end of Chapter 6, is intended to cope with. The idea, in theory, is that your app should behave the same way when it comes to the front, regardless of whether it was terminated or merely suspended. We all know from experience, however, that this goal is difficult to achieve, and Apple’s own apps are noteworthy for failing to achieve it; for example, when Apple’s Books app comes to the front, it is perfectly obvious from its behavior and appearance whether it was terminated or merely suspended in the background.

Over time, successive iOS systems have complicated the picture. A modern iPad that does iPad multitasking (Chapter 9) is capable of running two apps at once: they are both active (in the foreground) at the same time.

A further complication is that your app can be backgrounded without being suspended. This is a special privilege, accorded in order that your app may perform a limited range of highly focused activities. For example, an app that is playing music or tracking the device’s location when it goes into the background may be permitted to continue doing so in the background.

In addition, an app that has been suspended can be woken briefly, remaining in the background, in order to receive and respond to a message — in order to be told, for example, that the user has crossed a geofence, or that a background download has completed. (See Chapters 14, 21, and 23.)

There is also an intermediate state in which your app can find itself, where it is neither frontmost nor backgrounded. This happens, for example, when the user summons the control center or notification center in front of your app. In such situations, your app may be inactive without actually being backgrounded.

Your app’s code can thus be running even though the app is not frontmost. If your code needs to know the app’s state in this regard, it can ask the shared UIApplication object for its applicationState (UIApplication.State), which will be one of these:

  • .active

  • .inactive

  • .background

Your app can, in fact, opt out of background suspension: you set the “Application does not run in background” key (UIApplicationExitsOnSuspend) to YES in your Info.plist, and now the Home button does terminate your app, just as in the pre–iOS 4 days. It’s improbable, however, that you would want to do that (though it could make sense for some apps).

App Delegate Events

When your app launches, the UIApplicationMain function creates its one and only UIApplication instance as the shared application object, along with the app delegate, which adopts the UIApplicationDelegate protocol (see “How an App Launches”). The application then proceeds to report lifetime events to its delegate through calls to the methods declared in that protocol. (Other objects can also register to receive some of these events as notifications.)

The suite of basic application lifetime events that may be sent to your app delegate is surprisingly limited and considerably less informative than one might have hoped. The events are as follows:

application(_:didFinishLaunchingWithOptions:)

The app has started up from scratch. You’ll typically perform initializations here. If an app doesn’t have a main storyboard, or is ignoring the main storyboard at launch time, this code must also ensure that the app has a window, set its root view controller, and show the window (see Appendix B).

(Another event, application(_:willFinishLaunchingWithOptions:), arrives even earlier. Its purpose is to allow your app to participate in the state saving and restoration mechanism discussed in Chapter 6.)

applicationDidBecomeActive(_:)

The app is now well and truly frontmost. Received after application(_:didFinishLaunchingWithOptions:). Also received after the end of any situation that caused the app delegate to receive applicationWillResignActive(_:).

applicationWillResignActive(_:)

The app is entering a situation where it is neither frontmost nor backgrounded; it will be inactive. Perhaps something has blocked the app’s interface — for example, the screen has been locked, or the user has summoned the notification center. A local notification alert or an incoming phone call could also cause this event. Whatever the cause, the app delegate will receive applicationDidBecomeActive(_:) when this situation ends.

Alternatively, the app may be about to go into the background (and will then probably be suspended); in that case, this event was purely transient, and applicationDidEnterBackground(_:) will follow almost immediately.

applicationDidEnterBackground(_:)

The application has been backgrounded. Always preceded by applicationWillResignActive(_:). Your app will now probably be suspended; before that happens, you have a little time to finish up last-minute tasks, such as relinquishing unneeded memory (see Chapter 6), and if you need more time for a lengthy task, you can ask for it (see Chapter 24).

applicationWillEnterForeground(_:)

The application was backgrounded, and is now coming back to the front. Always followed by applicationDidBecomeActive(_:). Note that this message is not sent on launch, because the app wasn’t previously in the background.

applicationWillTerminate(_:)

The application is about to be killed dead. Surprisingly, even though every running app will eventually be terminated, it is quite unlikely that your app will ever receive this event (unless it has opted out of background suspension, as I explained earlier). The reason is that, by the time your app is terminated by the system, it is usually already suspended and incapable of receiving events. (I mentioned an exceptional case in Chapter 14, and I’ll mention some more in the next section.)

App Lifetime Scenarios

A glance at some typical scenarios will demonstrate the chief ways in which your app delegate will receive app lifetime events. I find it helpful to group these scenarios according to the general behavior of the events.

Major State Changes

During very significant state changes, such as the app launching, being backgrounded, or coming back to the front, the app delegate receives a sequence of events:

The app launches from scratch

Your app delegate receives these messages:

  • application(_:didFinishLaunchingWithOptions:)

  • applicationDidBecomeActive(_:)

The user clicks the Home button

If your app was frontmost, your app delegate receives these messages:

  • applicationWillResignActive(_:)

  • applicationDidEnterBackground(_:)

The user summons your backgrounded app to the front

Your app delegate receives these messages:

  • applicationWillEnterForeground(_:)

  • applicationDidBecomeActive(_:)

If the user summons your backgrounded app to the front indirectly, another delegate message may be sent between those two calls. For example, if the user asks another app to hand a file over to your app (Chapter 22), your app delegate receives the application(_:open:options:) call between applicationWillEnterForeground(_:) and applicationDidBecomeActive(_:).

The screen is locked

If your app is frontmost, your app delegate receives these messages:

  • applicationWillResignActive(_:)

  • applicationDidEnterBackground(_:)

The screen is unlocked

If your app is frontmost, your app delegate receives these messages:

  • applicationWillEnterForeground(_:)

  • applicationDidBecomeActive(_:)

Paused Inactivity

Certain user actions effectively pause the foreground-to-background sequence in the middle, leaving the app inactive and capable of being either backgrounded or foregrounded, depending on what the user does next. Thus, when the app becomes active again, it might or might not be coming from a backgrounded state. For example:

The user double-clicks the Home button

The user can now work in the app switcher interface. If your app is frontmost, your app delegate receives this message:

  • applicationWillResignActive(_:)

The user, in the app switcher, chooses another app

If your app was frontmost, your app delegate receives this message:

  • applicationDidEnterBackground(_:)

The user, in the app switcher, chooses your app

If your app was frontmost, then it was never backgrounded, so your app delegate receives this message:

  • applicationDidBecomeActive(_:)

The user, in the app switcher, terminates your app

If your app was frontmost, your app delegate receives these messages:

  • applicationDidEnterBackground(_:)

  • applicationWillTerminate(_:)

This is one of the few extraordinary circumstances under which your app can receive applicationWillTerminate(_:), perhaps because it was never backgrounded long enough to be suspended.

The user summons the control center or notification center

If your app is frontmost, your app delegate receives this message:

  • applicationWillResignActive(_:)

The user dismisses the control center or notification center

If your app was frontmost, your app delegate receives this message:

  • applicationDidBecomeActive(_:)

But if the user has summoned the notification center, there’s another possibility: the user might tap a notification alert or a today widget to switch to that app. In that case, your app will continue on to the background, and your app delegate will receive this message:

  • applicationDidEnterBackground(_:)

The user holds down the screen-lock button

The device offers to shut itself off. If your app is frontmost, your app delegate receives this message:

  • applicationWillResignActive(_:)

The user, as the device offers to shut itself off, cancels

If your app was frontmost, your app delegate receives this message:

  • applicationDidBecomeActive(_:)

The user, as the device offers to shut itself off, accepts

If your app was frontmost, the app delegate receives these messages:

  • applicationDidEnterBackground(_:)

  • applicationWillTerminate(_:)

Transient Inactivity on the iPad

There are certain circumstances where your app may become inactive and then active again in quick succession. These have mostly to do with multitasking on the iPad. If this happens, your app delegate may receive these messages:

  • applicationWillResignActive(_:)

  • applicationDidBecomeActive(_:)

Here are some examples:

The user summons the dock

The results in my testing are inconsistent. Sometimes the app delegate indicates that we pass through transient inactivity; most of the time, nothing happens. Either way, your app ultimately remains active while the dock is present.

The user drags an app from the dock into slideover or splitscreen position

The results in my testing are similar to the previous case: sometimes there is transient inactivity, sometimes nothing happens, but either way, the app ultimately remains active. But on older devices, putting an app into slideover mode in front of your app causes applicationWillResignActive(_:) to be called.

The user toggles between split sizes

The app undergoes transient inactivity. As I mentioned in Chapter 9, if your view controller is notified of a change of size and possibly trait collection, this will happen during the period of transient inactivity, while the shared application’s state is .inactive.

Lifetime Event Timing

The app delegate messages may be interwoven with the lifetime events received by other objects. View controller lifetime events (“View Controller Lifetime Events”) are the most notable case in point. For example, there are circumstances where the root view controller may receive its initial lifetime events, such as viewDidLoad: and viewWillAppear(_:), before application(_:didFinishLaunchingWithOptions:) has even finished running (which may come as a surprise).

Different systems can also introduce changes in timing. For example, when I started programming iOS, back in the days of iOS 3.2, I noted the opening sequence of events involving the app delegate and the root view controller; they arrived in this order:

  1. application(_:didFinishLaunchingWithOptions:)

  2. viewDidLoad

  3. viewWillAppear(_:)

  4. applicationDidBecomeActive(_:)

  5. viewDidAppear(_:)

Relying on that order, I would typically use the root view controller’s viewDidAppear(_:) to register for UIApplication.didBecomeActiveNotification in order to be notified of subsequent activations of the app.

That worked fine for some years. However, iOS 8 brought with it a momentous change: the app delegate now received applicationDidBecomeActive(_:) after the root view controller received viewDidAppear(_:), like this:

  1. application(_:didFinishLaunchingWithOptions:)

  2. viewDidLoad

  3. viewWillAppear(_:)

  4. viewDidAppear(_:)

  5. applicationDidBecomeActive(_:)

This was a disaster for many of my apps, because the notification I had just registered for in viewDidAppear(_:) arrived immediately.

Then, in iOS 9, the order returned to what it was in iOS 7 and before — knocking my apps into confusion once again. Then, in iOS 11, the order reverted back to what it was in iOS 8!

Such capricious changes from one system version to the next are likely to pose challenges for the longevity and backward compatibility of your app. The moral is that you should not, as I did, rely upon the timing relationship between lifetime events of different objects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.157.186