iOS 4 is muscled up with tons of exciting new features. We’ve covered the Game Kit framework, local notifications, the Event Kit framework, GCD, and iAd in previous chapters. In this chapter, it’s time to introduce multitasking! Multitasking is a feature that has been requested on iOS since its first release. But there’s limited screen size and battery life on the iPhone or iPad, so generally only one application is visible and active at a time. In iOS 4 and later, with multitasking introduced, applications can continue running in the background.
What’s multitasking? It’s one of the important and exciting API features in iOS 4. When the user quits an application, instead of terminating, the application will enter the background state; you can use this feature to support fast app switching and running tasks in the background.
In this chapter, we’ll first cover the multitasking basics, the application’s lifecycle on iOS 4, followed by the background state and best practices based on an app’s lifecycle transitions. Then we’ll talk about how to adopt fast app switching on the iPhone and iPad and how to update the application interface with the correct user data when the application gets restarted from the background state. Finally, we’ll use a demo app to explain how to finish a task in the background with an expiration handler. First, let’s explore some basics on multitasking.
Multitasking is the ability to let the device run more than one application at once. For example, you may want to listen to music using the Pandora application while performing other tasks such as checking your email.
In the past, Apple had a few arguments against multitasking on iOS; the primary ones were that it slows down the device, degrades the battery life, and makes for an overall poor user experience. If you allow the user to run too many applications at once, the device will eventually run out of memory and start killing those applications. Running many applications at the same time quickly drains battery life. Because running an app requires processing power, the constant strain on the processor will soon result in a drained battery.
In iOS 4, Apple has implemented the ability for programs to run in the background. Figure 21.1 shows the multitasking UI on the iPhone and iPad. This isn’t true multitasking per se, but it’s getting closer and addresses most of users’ other gripes.
Applications that need to execute operations in the background are restricted to certain tasks, including the following:
By default, all applications that are compiled for iOS 4 will support multitasking. But after the user taps the Home button, the app will go into the background suspended state unless the background running mode is added.
Because most applications don’t require constant usage, the system automatically puts them into a suspended state when the user exits them. When the user resumes the application, it should load into the last state it was in when the user exited it. This is more of a fast application-switching model. A great comparison is that when you’re reading a book, you may want to take a break and get a cup of coffee, so you put a bookmark at the page where you stopped. When you return, you can continue from the exact same page where you left off.
Next we’ll look at the application lifecycle and how to enable multitasking on iOS 4.
With iOS multitasking support, the application lifecycle expands to not running, running in the foreground, and running in the background, as shown in figure 21.2. When the application launches, it will move from the not-running state to the foreground, stay inactive briefly, and become active. That’s when the MainWindow.xib file gets loaded into the application. While the application is running in the foreground state, an SMS message or an incoming call could pop out and interrupt the current application, which therefore would become inactive.
When the user taps the Home button, the application will move from the foreground state to the background. The application may continue running if the background mode is supported on the device (iPhone 3G and iPod Touch 2nd Gen don’t support the background state at all), such as background audio. When a user is listening to their iPod and taps the Home button, the application still plays music in the background state.
Most applications will stay in the suspended state after moving to background mode and won’t execute code in the suspended state. When the user switches back to this application after some time, it will automatically return from the background to the foreground state, where it was left previously.
Sometimes, when the system is running low on memory, applications with heavy memory usage in the background are terminated in order to free up more memory for the foreground application. Another possibility is that the user will manually terminate the application from the multitasking UI stack.
In iOS 4, it’s important to understand the application’s lifecycle in order to design a responsive and smooth-transitioning application. For example, when you’re designing a game application, you should pause the game when there’s an incoming call and save important data before the application quits or moves to the background state. How can you monitor the application’s lifecycle during runtime?
Two solutions are available on iOS 4. One solution is to respond to each major callback method in the application delegate. Another way is to observe the notifications from the notification center. Table 21.1 has a complete list of application delegate methods and notifications.
Method |
Notification |
Description |
---|---|---|
application:didFinishLaunchingWithOptions: | UIApplicationDidFinishLaunchingNotification | The application launches. |
application-DidBecomeActive: | UIApplication-DidBecomeActiveNotification | The application runs actively in the foreground. |
application-WillResignActive: | UIApplication-WillResignActiveNotification | During interruption, the application becomes inactive. |
application-DidEnterBackground: | UIApplication-DidEnterBackgroundNotification | The application enters the background state. |
application-WillEnterForeground: | UIApplication-WillEnterForegroundNotification | The application resumes from the background. |
application-WillTerminate: | UIApplication-WillTerminateNotification | The application is terminated and not running. |
Inside the application’s delegate, you can monitor the application’s lifecycle state and transitions; at the time of writing, six major callbacks are available in the application delegate:
You can use the notifications in table 21.1 to monitor the application’s transition states with the notification center. For example, you can use the following code snippet to register a notification for UIApplicationWillResignActiveNotification inside the view controllers:
NSNotificationCenter *notifCenter = [NSNotificationCenter defaultCenter]; [notifCenter addObserver:self selector:@selector(resignActive:) name:UIApplicationWillResignActiveNotification object:nil];
As you can tell, in order to smoothly support multitasking in the application, you need to consider saving the application’s data properly before the application moves to the background and reload the necessary data before the application launches from the background mode.
You may have more questions related to the background state at this point, which is important to understand for multitasking in iOS 4. We’ll take a closer look at the application’s background state in the following section.
The applications built under iOS 4 are automatically enabled for multitasking. But in order to support certain constantly running types of multitasking, the background mode must be declared in advance. To do so, include the UIBackgroundModes key in the application’s Info.plist file. Table 21.2 contains a detailed list for the multitasking mode.
UIBackgroundModes |
Descriptions |
---|---|
Audio | The application plays audible content to the user while in the background, such as the music application Pandora or a turn-by-turn audio navigation application. |
Location | The application keeps the user informed about current location updates, even while running in the background. This mode is designed for a turn-by-turn navigation application. Note that a significant location change or region-monitoring location service doesn’t have to register for the location mode because the specific location update isn’t required. |
VoIP | The application provides the ability for the user to make phone calls using an internet connection. |
The value for the UIBackgroundModes key is an array that may contain one or more strings. For example, the turn-by-turn navigation application will need both location service and audio running in the background mode, so you must add both audio and location to the Info.plist. We’ll touch on how to monitor significant location changes in the background in section 21.5 and continue with advanced background audio in the next chapter.
Background state is important for iOS 4. In this section, we’ll look at the definition of the background state under iOS 4 and explore the application lifecycle with the background state.
When the application is about to enter the background state, a couple of application delegate methods will be called. The process flow chart in figure 21.3 demonstrates the process. When user taps the Home button, the application will move from the foreground to the background. The application delegate method application-DidEnterBackground: gets called.
The application delegate method applicationDidEnterBackground: has only about 6 seconds to finish the task; then it may enter the suspended state. You want to make sure your application performs some best practices before the application transitions to the background:
After the application continues running in the background for about 6 seconds, the system will move the application to the suspended state. The system takes a snapshot of the application’s current image, and the application can’t perform any task or execute code after it’s suspended.
How does the application resume in the foreground? When the user launches an application that’s in the background state, the application delegate sends a message to the applicationWillEnterForeground: method in order to restart any services or tasks. Once the application resumes running in the foreground, a second message, applicationDidBecomeActive:, will be sent, as shown in figure 21.4.
In the stage when the application moves from the background to the foreground, you should undo the changes made before the application entered the background. For example, OpenGL framework objects need to be re-created. You may also want to respond to screen-rotation events and update the user interface.
The background state provides many possibilities for applications on iOS. But if for some reason you wish to turn off the background state, you can add the UIApplicationExitsOnSuspend key to your application’s Info.plist file and set its value to YES. If your application does opt out of the background state, the application will directly move to the not-running state once the user taps the Home button, which means the applicationWillTerminate: method of the application delegate is called instead.
In this section, you learned the basics of multitasking on iOS 4, saw how to monitor the application’s state change in the lifecycle, and thoroughly explored the background state. Next, you’ll practice writing real code for multitasking.
As we mentioned earlier, fast app switching offers a great user experience. When users want to switch between two apps, they double-tap the Home button to enable the multitasking UI, as shown previously in figure 21.1. All applications built on iOS 4 will support fast app switching automatically—no additional setup is required. In this section, you’ll build an application that will handle fast app switching smoothly.
Start Xcode, and create a new project with the iOS template Navigation-Based Application. Name the application Quick. In this application, you want to keep track of each time the application launches from the not-running state or resumes from the background state. All the data collected will be listed on the table view.
In order to collect user data every time the application starts, you’ll use the application delegates application:didFinishLaunchingWithOptions: and application-WillEnterForeground: to save the launching time. Single-click the QuickAppDelegate.m file, and add the changes from the following listing.
In the application delegate, you want to get the system time when the application starts. You define the method getCurrentTime to get the system time using NSDate and convert the time to string format including the date and time as “yyyy/MM/dd HH:mm:ss”; then you define another method, saveCurrentData , to save the launch time with NSUserDefaults, which you learned how to do in chapter 8. There are two callback methods in the application delegate related to the application launch time. First is application:didFinishLaunchingWithOptions:. It occurs when the application initially launches from the not-running state, so you want to save the new launch data here. The second method, in applicationWillEnterForeground: , occurs when the application relaunches from the background to continue running in the foreground state, so you need to save this new user data as well.
You’ve now prepared the application delegate for collecting the launch time data; in the next section you’ll make sure the table view can display the user data correctly.
Single-click the RootViewController.h file, and add in the changes from the following code snippet:
#import <UIKit/UIKit.h> @interface RootViewController : UITableViewController { NSArray *events; } @property (nonatomic, retain) NSArray *events; @end
You’ll use the array events as the table view’s data source. Move to the RootViewController.m file. You want to make sure that each time the application launches, the table view will reload the data that you’ve saved inside the application delegate, as shown in the following listing.
In listing 21.2 you use the array events as the table view’s data source. Each row will display the launching date you’ve saved in the application delegate methods. First, you create a new method updateUI to reload the data from NSUserDefaults and update the table view’s data and the table view’s user interface. When the table view controller is first created, you need to load the data with the method updateUI . Then you define the notification to get notified by the system in order to reload the table view’s data when the application relaunches from the background. Inside the table view delegate methods, you define the table view’s row number and display content. Then, when the view controller is released, you need to release the retained object events in the memory.
With all the new changes included, save the project. Build and launch the application. You should see a UI display similar to the one shown in figure 21.5.
Quit the application, double-tap the Home button when the multitasking UI appears, relaunch the application, and notice that the new time stamp is successfully added to the table view even though the application is launched from the background.
Congrats: you’ve learned how to save the application’s user data inside the application delegate callback methods and how to update the user interface when the application launches from background to foreground by using the notification inside the view controller. You can use this demo application to test out different application state changes. Playing around with different cases will help you understand how to update the user interface and respond to changes correctly.
Next, we’ll look at a more in-depth use for the multitasking API: how to finish a task in the background.
In the previous sections, we went through multitasking basics and fast app switching. Most of the time, it will be good enough for an application to use only fast app switching. But some applications may need extra time to finish the task in the background state before being suspended: for example, uploading a photo to the cloud, tweeting your current status, or downloading the latest RSS feeds. When the user taps the Home button, the application can continue running for about 6 seconds in the background before the application is suspended. In this section, we’ll look at the task-completion API and build a demo app for practice.
With the task completion in the background, the user doesn’t have to wait until the task is completed, but you need to tell the application how to start and when to end the task in the background. How can you do that? iOS 4 provides a pair of methods under UIApplication to wrap up a big task in between. You can begin the task assignment by calling beginBackgroundTaskWithExpirationHandler:, and when it’s done, you call the method endBackgroundTask: to end the task.
Here are the basics to use the task-completion API:
As you can tell, the task-completion API is easy to follow. The remaining time in the background for task completion can be accessed from the UIApplication’s property: backgroundTimeRemaining. In case a few tasks are waiting to be done, you can use this property to check the remaining time in between tasks.
Back in section 21.1, you learned about the application’s lifecycle in iOS 4. When the application moves from the foreground to the background state, its delegate method applicationDidEnterBackground: will be called.
Listing 21.3 is an example of a task-completion API, showing how to finish a task in the background before the applications gets suspended. You can create a window-based project from the iOS template project in Xcode, because this part of API will only make changes to the app delegate file. In the app delegate header file, add in the task identifier: UIBackgroundTaskIdentifier bgTask. Define bgTask as a property of the app delegate, and synthesize it in the.m file.
Next, add the new changes from the following listing to the app delegate file.
In this application, you want to finish a task, and after completion, send a local notification out to notify the user that the work is done. This example uses a simple method uploadPhoto to demonstrate the local notification setup in the background state. If you’re interested, you can look up Flickr’s photo-uploading API and add real photo uploading code into this method.
Because the main purpose of this listing is to demonstrate the task-completion API, let’s take a closer look at how the job is done here. In the method applicationDidEnterBackground: you start the bgTask by calling the method beginBackgroundTaskWithExpirationHandler . The argument for the expiration handler is a block to end the task and release the bgTask. Then, with the help of Grand Central Dispatch (GCD), the task uploadPhoto gets called, and you end the task by calling the method endBackgroundTask: and release the task identifier by assigning the UIBackgroundTaskInvalid value to bgTask . We covered the GCD back in chapter 16.
Save the changes, and build and run the application. Tap the Home button to let the application enter the background. When the application completes the task in the background, it will send out a local notification, as shown in figure 21.6.
Cool! You just managed to finish a task in the background. Always keep in mind that the permission and time limit are not guaranteed when you use the task-completion API. That’s why it’s a good practice to end the task gracefully instead of letting it get suspended when it doesn’t finish in time.
Next, we’ll cover how to monitor location changes in the background on iOS 4.
We covered the Core Location framework in chapter 10 and the Map Kit framework in chapter 18. Because iOS provides an easy-to-use Core Location framework and Map Kit framework, developers have the chance to build creative and amazing applications. For example, Foursquare is one of the most-famous location-based social applications. With iOS 4’s location service, you can even check in to places through Foursquare while the app isn’t running. You may ask, “How did they do that? Will the battery drain quickly because the location service consumes a lot of power?”
In iOS 4, an application can monitor location changes even though the application isn’t running. The best part is, you don’t need to worry about battery usage in this case. In this section, we’ll first look at the techniques behind background location monitoring and then explore the new API under iOS 4 for background location monitoring.
As mentioned in chapter 10, the location service will use more battery power when the accuracy requirement is higher. Of the three location service providers—cell phone tower, wi-fi, and GPS—cell phone tower uses the least power and provides acceptable accuracy (the 300-meter accuracy is good enough for the majority of cases).
There are several ways to track the user’s location in the background, some of which don’t require the application running regularly in the background:
If your application doesn’t need to provide precise and continuous location information to the user, the significant location change service works out the best. This service provides location updates in the background and can even wake up a suspended or not-running application. Combine this feature with the region monitor in the core location, and you can create an awesome application.
In case the application requires precise and continuous location data, such as a navigation application, you need to declare the location service in the Info.plist. Keep in mind that it increases power consumption because of the GPS usage, but it’s available for applications that truly need it. The device should be plugged into the power supply in the real-time case.
We focused on the significant location change service here, mainly because the other options are the same in the background as the regular location updates in the foreground, which we covered in chapter 10.
The significant location change service offers a low-power location service for devices with cellular radios. The location data isn’t as accurate as that of the GPS, but it’s suitable for most cases. For example, you can use this background location service to trace how far the user has driven their car.
Use the method significantLocationChangeMonitoringAvailable to check if the current device supports significant location change monitoring. You can use the following code snippet to verify the availability:
BOOL available = [CLLocationManager significantLocationChangeMonitoringAvailable];
When this service is available, you can use two methods in CLLocationManager to start and stop receiving the updates:
-(void)startMonitoringSignificantLocationChanges; -(void)stopMonitoringSignificantLocationChanges;
Remember to turn off this service when you no longer need to receive the updates. Otherwise, the system will still wake up the application whenever new updates are available. That may annoy the user because it’s unnecessary.
When the application isn’t running, the new location updates will wake up the application. How can you handle this update in the application? Remember the application delegate method in the application’s lifecycle that we discussed in section 21.1? In this case, the application:didFinishLaunchingWithOptions: method gets called, and the dictionary launchOptions contains the UIApplicationLaunchOptionsLocationKey key. You can use this method to restart the CLLocationManager if it’s not available. You can use the CLLocationManager delegate method location-Manager:didUpdateToLocation:fromLocation: to get the new location data.
The region-based location updates service works exactly the same way as the significant location updates. The new location update wakes up the application, even though the application may be suspended or not running at all. You can use two methods available in CLLocationManager to start and stop the monitoring for a region:
- (void)startMonitoringForRegion:(CLRegion *) desiredAccuracy:(CLLocationAccuracy); - (void)stopMonitoringForRegion:(CLRegion *);
Before using the region-based location monitoring service, make sure the application has this service available. For the hardware availability, you can use the CLLocation-Manager class method + (BOOL)regionMonitoringAvailable;.
But even if the device can support the region-based location monitoring service, the user can enable or disable the region monitoring service in the settings. Therefore, you also need to check to see if this service is currently enabled. Use the CLLocationManager class method regionMonitoringEnabled to do so.
The region-based location service will send the new location to the CLLocation-Manager’s delegate methods. Table 21.3 lists the important delegate methods for the region-monitoring location service.
Description |
Methods |
---|---|
User entered the specified region. | -(void)locationManager:(CLLocationManager *) manager didEnterRegion:(CLRegion *)region; |
User left the specified region. | -(void)locationManager:(CLLocationManager *) manager didExitRegion:(CLRegion *)region; |
A region monitoring error occurred. | -(void)locationManager:(CLLocationManager *) manager monitoringDidFailForRegion: (CLRegion *) region withError:(NSError *)error; |
The region-based location service can be helpful and is easy to implement. For example, you can use this service to notify the user that a grocery store is nearby, or in a todo list application you can remind the user to pick up clothes from the dry cleaner when they are near it.
Multitasking is useful in many ways. With the application running in the background, you can provide an amazing user experience with the iOS 4 platform. Instead of yanking the user out of the application when the app quits, with a little extra work you can have the application return to the previous state or, even better, perform operations without the application running in the foreground.
Try to adopt fast app switching in your application. It will give the user a chance to resume the application.
In the next chapter, we’ll spend more time on continuous location updates in the background and build an application together. We’ll also tackle multitasking in depth; we’ll show how to enable audio playing in the background and control the background audio through remote-control events.