Today we’re going to continue updating the Demine project for the iOS 4.0 environment: we’re going to begin to add support for multitasking. Even though Demine is not going to be doing any background processing, it turns out that a multitasking environment mandates certain changes to the program, as we shall soon see.
The introduction of multitasking forces the developer to address three major new requirements:
- Applications that have been moved into the background should be “well-behaved”; they should reduce their resource usage so that they are not killed by the system
- Applications must not expect notifications when they are terminated; under the multitasking regime applications are notified when they are moved into the background, but they are not notified when they are suspended, and not notified if they are terminated from a suspended state (as they will be in the now-normal run of things)
- Applications must be prepared to handle any relevant system changes that occur while they are suspended
AAPL provides two checklists for developers who wish to support multitasking. The first is deceptively brief, because one of its items amounts to “follow this entire second checklist”. Nevertheless, in the interest of narrative coherence, let’s begin with that first checklist. As we go along, we’ll be making any necessary changes to the project as we left it last week; you can download that here.
[Applications must respond] appropriately to the state transitions that occur while running under multitasking. Applications need to observe these transitions in order to save state and tailor their behavior for foreground or background execution. Not handling these transitions properly could lead to data loss or improper behavior. For more information about the states and transitions, see “Understanding an Application’s States and Transitions.”
That “respond appropriately” language covers a lot of ground; in fact, it’s really too vague to drive concrete action. We’ll have to wait for our second checklist before we actually do anything. For now, let’s look at what state transitions we’ll have to consider:
- Becoming inactive. The “inactive” state is a foreground state in which the application is blocked from receiving events, as when the device notifies the user that an SMS message has arrived. The application is notified when it enters the “inactive” state from the “active” state via the:
applicationWillResignActive:application delegate method
- Becoming active. The “active” state is the foreground state in which users interact with the application. The application is notified when it enters the “active” state via the:
applicationDidBecomeActive:application delegate method
- Entering a background state. The “background” states are those in which the application is present in memory, but disconnected from the user interface. (Note that, confusingly, there are two “background” states, named “background” and “suspended”. Here we’re discussing the 2-state “background” family.) The application is notified when it enters a “background” state via the:
applicationDidEnterBackground:application delegate method
- Entering a foreground state. The “foreground” states are the previously-discussed “active” and “inactive” states, and the application is notified when it enters one of these states from a “background” state via the:
applicationWillEnterForeground:application delegate method
- Termination. The program is notified when it is terminated from any non-“suspended” state (for instance, if it is running on a non-multitasking device, or on iOS 3.0), via the:
applicationWillTerminate:application delegate method
[Applications must follow] the guidelines for behavior when moving to the background. These guidelines are there to help your application behave correctly while in the background and in situations where your application might need to be terminated. For information about these guidelines, see “Being a Responsible, Multitasking-Aware Application.”
Practically, there’s nothing to do here until we come to the second checklist, which we will see next week. Editorially, I’ll say that I don’t think that this is a high point in AAPL’s documentation — especially since the guidelines/second checklist are on a page headed with this text:
Important: If you need only minimal support for running under multitasking, and do not actually need to perform tasks while in the background, you do not need to read this chapter. Information about how to design your application to support the multitasking environment is available in “Multitasking.”
(The link sends you to the checklist we’re now working through, which sends you right back to the stuff you supposedly don’t need to read. It’s confusing and exasperating.)
Monitoring System Changes
[Applications may register] for any notifications that report system changes your application needs. The system queues notifications while an application is suspended and delivers them once the application resumes execution so that it can make a smooth transition back to execution. For more information, see “Responding to System Changes While in the Background.”
This addresses the fact that multitasking raises two troublesome new possibilities WRT system changes:
- Changes that your application was always interested in can now happen while your app is suspended; it must be prepared to handle them when it resumes processing. (Note that this assumes that all relevant notifications will be delivered after the application resumes.)
- Multitasking allows a number of previously-impossible changes to occur while your app is running. For instance, a user can now suspend your app, change his locale through the Settings application, and then resume your app. You must consider whether such newly-possible changes are relevant to your application.
For Demine, the only system change we’ve been keeping an eye on heretofore has been the reachability of the iTunes store. Although we do expect to receive a notification when this changes, the notification we process isn’t a system-generated one, and therefore likely isn’t queued for delivery while the app is suspended. We depend on a callback being fired from the
SystemConfiguration framework, and it’s not immediately clear whether or not the machinery behind that callback will be set in motion if reachability changes while the app is suspended. Experimentally, however, it appears that the callback is fired, which is nice. Even if the mechanism behind it is pretty obscure.
As for newly possible changes … well, there’s no really tidy way to think about them. AAPL provides a list, but, as we just saw with reachability, there is reason to believe that it is not exhaustive. Mulling it over, I think that locale changes might be a problem, but I don’t believe that we need worry about anything else. Let’s talk about locale a bit, though.
AAPL has this to say on the subject of locale changes:
If the user changes the language or locale of the device while your application is suspended, the system notifies you of that change using the
NSCurrentLocaleDidChangeNotificationnotification. You can use this notification to force updates to any views containing locale-sensitive information, such as dates, times, and numbers. Of course, you should also be careful to write your code in ways that might make it easy to update things easily:
- Use the
autoupdatingCurrentLocaleclass method when retrieving
NSLocaleobjects. This method returns a locale object that updates itself automatically in response to changes, so you never need to recreate it.
- Avoid caching
NSFormatterobjects. Date and number formatters must be recreated whenever the current locale information changes.
We do create several formatter objects in the code, but either hard-code their formatting irrespective of locale, or set their locale based on a
priceLocale member. We’re not out of the woods yet, however. First of all, it’s not clear whether or not the
priceLocale members will (or should) change when a user alters his settings. Secondly, we reference
localizedDescription members, and the documentation claims that both are “localized based on the currentLocale property” — i.e., they’ll be improperly localized if the user has updated his locale.
In order to handle this, I’m going to trigger a reload of the Store whenever an
NSCurrentLocaleDidChangeNotification is received. This change is complicated by the fact that the store may be loading, or restoring, or displaying an item detail page, or in mid-purchase, but we’ll muddle through. For a complete description of the required changes, please consult the diff. Below, I present an overview of the relevant considerations:
- I’ve chosen to perform a complete refresh of the store from iTunes when handling locale changes. I’m not sure if this is necessary, but it’s certainly sufficient.
- To perform the reload, we add a
locateChanged:method to the
Storecontroller, and call it on an
- Since the store might be displaying an
Itemdetail page, we add some logic to the
productsRequest:didReceiveResponse:method to check for this, and to update the
- In order to make that last bit work, we make the
producta property, and add logic to update the relevant views when it changes.
Storecontroller might be in the middle of a load or restore operation (and displaying appropriate GUI widgets) when the locale changes; in such cases we just queue up a request for a subsequent reload. There’s a fair bit of fiddly code to make all this work.
I realize this section has run a little long. I wanted to demonstrate just how hairy things can get when considering the implications of system changes in general, and locale changes in particular. The set of things you need to worry about isn’t cut-and-dried; surprising hazards may lurk anywhere.
If you want to do actual work while in the background, you need to request permission to continue running. For more information about the types of work you can perform, and how to request permission to do that work, see “Executing Code in the Background.”
Demine doesn’t benefit from background processing, so we can just skip this part. With that, we come to the end of the first checklist.
The second checklist is much meatier, and we will see that next week, when we will (hopefully) conclude this project. (I had hoped to wrap things up this week, but this post ran long, and the design and testing of the
Store upgrades was a time-consuming hassle.) Until next week, you can download and peruse the complete project for this week’s post.