| John Ray Published by Sams ISBN-10: 0-672-33220-5 ISBN-13: 978-0-672-33220-3 |
What You'll Learn in This Hour:
- How iOS 4 supports background tasks
- What types of background tasks are supported
- How to disable backgrounding
- How to suspend applications
- How to execute code in the background
"The ability to run multiple applications in the background" mocks the Verizon commercial. "Why can't a modern operating system run multiple programs at once?" question the discussion groups. As a developer and a fan of the iPhone, I've found these threads amusing in their naiveté and somewhat confusing. The iPhone has always run multiple applications simultaneously in the background, but they were limited to Apple's applications. This restriction has been to preserve the user experience of the device as a phone. Rather than an "anything goes" approach, Apple has taken steps to ensure that the phone remains responsive at all times.
With the release of iOS 4.x, Apple answers the call from the competition by opening up background processing to third-party applications. Unlike the competitors, however, Apple has been cautious in how it approached backgrounding—opening it up to a specific set of tasks that users commonly encounter. In this hour's lesson, you learn several of the multitasking techniques that you can implement in iOS 4.
Understanding iOS 4 Backgrounding
If you've been working in iOS 4.x or later as you've built the tutorials in this book, you may have noticed that when you quit the applications on your phone or in the iPhone Simulator, they still show up in the iOS task manager, and, unless you manually stop them, they tend to pick up right where they left off. The reason for this is that projects created in iOS 4.x are background-ready as soon as you click the Build and Run button. That doesn't mean that they will run in the background, just that they're aware of the new iOS 4 background features and will take advantage with a little bit of help.
Before we examine how to enable backgrounding (also called multitasking) in our projects, let's first identify exactly what it means to be a background-aware application, starting with the types of backgrounding supported, then the application life cycle methods.
Types of Backgrounding
We explore four primary types of backgrounding in iOS 4.x: application suspension, local notifications, task-specific background processing, and task completion.
Suspension
When an application is suspended, it will cease executing code but be preserved exactly as the user left it. When the user returns to the application, it appears to have been running the whole time. In reality, all tasks will be stopped, keeping the app from using up your iPhone's resources. Any application that you compile against iOS 4.x will, by default, support background suspension. You should still handle cleanup in the application if it is about to be suspended (see "The Background-Aware Application Life Cycle" section, later in this chapter), but beyond that, it "just works."
In addition to performing cleanup as an application is being suspended, it will be your responsibility to recover from a background suspended state and update anything in the application that should have changed while it was suspended (time/date changes and so on).
Local Notifications
The second type of background processing is the scheduling of local notifications (UILocalNotification
). If you've ever experienced a push notification, local notifications are the same but are generated by the applications that you write. An application, while running, can schedule notifications to appear onscreen at a point in time in the future. For example, the following code initializes a notification (UILocationNotification
) configures it to appear in five minutes, and then uses the application's scheduleLocalNotification
method to complete the scheduling:
UILocalNotification *futureAlert;
futureAlert = [[[UILocalNotification alloc] init] autorelease];
futureAlert.fireDate = [NSDatedateWithTimeIntervalSinceNow:300];
futureAlert.timeZone = [NSTimeZonedefaultTimeZone];
[[UIApplication sharedApplication] scheduleLocalNotification:futureAlert];
These notifications, when invoked by iOS, can show a message, play a sound, and even update your application's notification badge. They cannot, however, execute arbitrary application code. In fact, it is likely that you will simply allow iOS to suspend your application after registering your local notifications. A user who receives a notification can click View button in the notification window to return to your application.
Task-Specific Background Processing
Before Apple decided to implement background processing, they did some research on how users worked with their handhelds. What they found was that there were specific types of background processing that people needed. First, they needed audio to continue playing in the background; this is necessary for applications like Pandora. Next, location-aware software needed to update itself in the background so that users continued to receive navigation feedback. Finally, VoIP applications like Skype needed to operate in the background to handle incoming calls.
These three types of tasks are handled uniquely and elegantly in iOS 4.x. By declaring that your application requires one of these types of background processing, you can, in many cases, enable your application to continue running with little alteration. To declare your application capable of supporting any (or all) of these tasks, you will add the Required Background Modes (UIBackgroundModes
) key to the project's plist file, and then add values of App Plays Audio (Audio), App Registers for Location Updates (Location), or App Provides Voice over IP Services (VoIP).
Task Completion for Long-Running Tasks
The fourth type of backgrounding that we'll be using in iOS 4.x is task completion. Using task-completion methods, you can "mark" the tasks in your application that will need to finish before it can be safely suspended (file upload/downloads, massive calculations, and so on).
For example, to mark the beginning of a long running task, first declare an identifier for the specific task:
UIBackgroundTaskIdentifier myLongTask;
Then use the application's beginBackgroundTaskWithExpirationHandler
method to tell iOS that you're starting a piece of code that can continue to run in the background:
myLongTask = [[UIApplicationsharedApplication]
beginBackgroundTaskWithExpirationHandler:^{
}];
And, finally, mark the end of the long-running task with the application endBackgroundTask
method:
[[UIApplication sharedApplication] endBackgroundTask:myLongTask];
Each task you mark will have roughly 10 minutes (total) to complete its actions, plenty of time for most common uses. After the time completes, the application is suspended and treated like any other suspended application.
The Background-Aware Application Life Cycle Methods
In Hour 4, "Inside Cocoa Touch," you started learning about the application life cycle, as shown in Figure 21.1. You learned that, in iOS 4.x, applications should clean up after themselves in the applicationDidEnterBackground
delegate method. This replaces applicationWillTerminate
in earlier versions of the OS, or as you'll learn shortly, in applications that you've specifically marked as not capable (or necessary) to run in the background.
Figure 21.1 The iOS 4.x application life cycle.
In addition to applicationDidEnterBackground
, there are several other methods that you should implement to be a proper background-aware iOS citizen. For many small applications, you won't need to do anything with these, other than leave them as is in the application delegate. As your projects increase in complexity, however, you'll want to make sure that your apps move cleanly from the foreground to background (and vice versa), avoiding potential data corruption and creating a seamless user experience.
Caution - It is important to understand that iOS can terminate your applications, even if they're backgrounded, if it decides that the device is running low on resources. You can expect that your applications will be fine, but plan for a scenario where they are forced to quit unexpectedly.
The methods that Apple expects to see in your background-aware apps are as follows:
application:didFinishLaunchingWithOptions
: Called when your application first launches. If your application is terminated while suspended, or purged from memory, needs to restore its previous state manually. (You did save it your user's preferences, right?)applicationDidBecomeActive
: Called when an application launches or returns to the foreground from the background. This method can be used to restart processes and update the user interface, if needed.applicationWillResignActive
: Invoked when the application is requested to move to the background or to quit. This method should be used to prepare the application for moving into a background state, if needed.applicationDidEnterBackground
: Called when the application has become a background application. This replaces applicationWillTerminate
in iOS 4.x. You should handle all final cleanup work in this method. You may also use it to start long-running tasks and use task-completion backgrounding to finish them.applicationWillEnterForeground
: Called when an application returns to an active state after being backgrounded.applicationWillTerminate
: Invoked when an application on a nonmultitasking version of iOS is asked to quit, or when iOS determines that it needs to shut down an actively running background application.
Method stubs for all of these exist in your iOS 4.x application delegate implementation files. If your application needs additional setup or teardown work, just add the code to the existing methods. As you'll see shortly, many applications, such as the majority of those in this book, require few changes.
Caution - The assumption in this hour's lesson is that you are using iOS 4.x or later. If you are not, using background-related methods and properties on earlier versions of the OS will result in errors. To successfully target both iOS 4.x and earlier devices, check to see whether backgrounding is available, and then react accordingly in your apps.
Apple provides the following code snippet in the iPhone Application Programming Guide for checking to see (regardless of OS version) whether multitasking support is available:
UIDevice* device = [UIDevice currentDevice];
BOOL backgroundSupported = NO;
if ([device respondsToSelector:@selector(isMultitaskingSupported)])
backgroundSupported = device.multitaskingSupported;
If the resulting backgroundSupported
Boolean is YES
, you're safe to use background-specific code.
Now that you have an understanding of the background-related methods and types of background processing available to you, let's look at how they can be implemented. To do this, we'll reuse tutorials that we've built throughout the book (with one exception). We won't be covering how these tutorials were built, so be sure to refer to the earlier hours if you have questions on the core functionality of the applications.
Disabling Backgrounding
We start with the exact opposite of enabling backgrounding: disabling it. If you think about it, there are many different "diversion" apps that don't need to support background suspension or processing. These are apps that you use and then quit. They don't need to hang around in your task manager afterward.
For example, consider the HelloNoun application in Hour 6, "Model-View-Controller Application Design." There's no reason that the user experience would be negatively affected if the application started from scratch each time you ran it. To implement this change in the project, follow these steps:
- Open the project in which you want to disable backgrounding (such as HelloNoun).
- Open the project's plist file in the resources group (HelloNoun-Info.plist).
- Add an additional key to the property list, selecting Application Does Not Run in Background (
UIApplicationExitsOnSuspend
) from the Key pop-up menu. - Click the check box beside the key, as shown in Figure 21.2.
- Save the changes to the plist file.
Figure 21.2 Add the Application Does Not run in Background (UIApplication-ExitsOnSuspend) key to the project.
Build and run the application on your iPhone or in the iPhone simulator. When you exit the application with the Home button, it will not be suspended, nor will not show in the task manager, and it will restart fresh when you launch it the next time.
Handling Background Suspension
In the second tutorial, we handle background suspension. As previously noted, you don't have to do anything to support this other than build your project with the iOS 4.x development tools. That said, we use this example as an opportunity to prompt users when they return to the application after it was backgrounded.
For this example, we update the ImageHop application from Hour 8, "Handling Images, Animation, and Sliders." It is conceivable (work with me here, folks!) that a user will want to start the bunny hopping, exit the application, and then return to exactly where it was at some time in the future.
To alert the user when the application returns from suspension, we'll edit the application delegate method applicationWillEnterForeground
. Recall that this method is invoked only when an application is returning from a backgrounded state. Open ImageHopAppDelegate.m and implement the method, as shown in Listing 21.1.
Listing 21.1
- (void)applicationWillEnterForeground:(UIApplication *)application {
UIAlertView *alertDialog;
alertDialog = [[UIAlertView alloc]
initWithTitle: @"Yawn!"
message:@"Was I asleep?"
delegate: nil
cancelButtonTitle: @"Welcome Back"
otherButtonTitles: nil];
[alertDialog show];
[alertDialog release];
}
Within the method, we declare, initialize, show, and release an alert view, exactly as we did in the "Getting Attention" tutorial in Hour 10, "Getting the User's Attention." After updating the code, build and run the application. Start the ImageHop animation, and then use the Home button to background the app.
After waiting a few seconds (just for good measure), open ImageHop again using the task manager or its application icon (not Build and Run!). When the application returns to the foreground, it should pick up exactly where it left off and present you with the alert shown in Figure 21.3.
Figure 21.3 The application WillEnterFore-ground method is used to display an alert upon returning from the background.
Implementing Local Notifications
Earlier in this lesson, you saw a short snippet of the code necessary to generate a local notification (UILocalNotification
). As it turns out, there's not much more you'll need beyond those few lines! To demonstrate the use of local notifications, we'll be updating Hour 10's "Getting the User's Attention" doAlert
method. Instead of just displaying an alert, it will also show a notification 5 minutes later and then schedule local notifications to occur every day thereafter.
Common Notification Properties
You want to configure several properties when creating notifications. A few of the more interesting of these include the following:
applicationIconBadgeNumber
: An integer that is displayed on the application icon when the notification is triggeredfireDate
: An NSDate
object that provides a time in the future for the notification to be triggeredtimeZone
: The time zone to use for scheduling the notificationrepeatInterval
: How frequently, if ever, the notification should be repeatedsoundName
: A string (NSString
) containing the name of a sound resource to play when the notification is triggeredalertBody
: A string (NSString
) containing the message to be displayed to the user
Creating and Scheduling a Notification
Open the GettingAttention application and edit the doAlert
method so that it resembles Listing 21.2. (Bolded lines are additions to the existing method.) Once the code is in place, we'll walk through it together.
Listing 21.2
1: -(IBAction)doAlert:(id)sender {
2: UIAlertView *alertDialog;
3: UILocalNotification *scheduledAlert;
4:
5: alertDialog = [[UIAlertView alloc]
6: initWithTitle: @"Alert Button Selected"
7: message:@"I need your attention NOW (and in alittle bit)!"
8: delegate: nil
9: cancelButtonTitle: @"Ok"
10: otherButtonTitles: nil];
11:
12: [alertDialog show];
13: [alertDialog release];
14:
15:
16: [[UIApplication sharedApplication] cancelAllLocalNotifications];
17: scheduledAlert = [[[UILocalNotification alloc] init] autorelease];
18: scheduledAlert.applicationIconBadgeNumber=1;
19: scheduledAlert.fireDate = [NSDate dateWithTimeIntervalSinceNow:300];
20: scheduledAlert.timeZone = [NSTimeZone defaultTimeZone];
21: scheduledAlert.repeatInterval = NSDayCalendarUnit;
22: scheduledAlert.soundName=@"soundeffect.wav";
23: scheduledAlert.alertBody = @"I'd like to get your attention again!";
24:
25: [[UIApplication sharedApplication] ÂscheduleLocalNotification:scheduledAlert];
26:
27: }
First, in line 3, we declare scheduledAlert
as an object of type UILocalNotification
. This local notification object is what we set up with our desired message, sound, and so on, and then pass off to the application to display sometime in the future.
In Line 16, we use [UIApplication sharedApplication]
to grab our application object, and then call the UIApplication
method cancelAllLocalNotifications
. This cancels any previously scheduled notifications that this application may have made, giving us a clean slate.
Line 17 allocates and initializes the local notification object scheduledAlert
. Because the notification is going to be handled by iOS rather than our GettingAttention application, we can use autorelease
to release it.
In line 18, we configure the notification's applicationIconBadgeNumber
property so that when the notification is triggered, the application's badge number is set to 1
to show that a notification has occurred.
Line 19 uses the fireDate
property along with the NSDate
class method dateWithTimeIntervalSinceNow
to set the notification to be triggered 300 seconds in the future.
Line 20 sets the timeZone
for the notification. This should almost always be set to the local time zone, as returned by [NSTimeZone defaultTimeZone]
.
Line 21 sets the repeatInterval
property for the notification. This can be chosen from a variety of constants, such as NSDayCalendarUnit
(daily), NSHourCalendarUnit
(hourly), and NSMinuteCalendarUnit
(every minute). The full list can be found in the NSCalendar
class reference in the Xcode developer documentation.
In Line 22, we set a sound to be played along with the notification. The soundName
property is configured with a string (NSString
) with the name of a sound resource. Because we already have soundeffect.wav available in the project, we can use that without any further additions.
Line 23 finishes the notification configuration by setting the alertBody
of the notification to the message we want the user to see.
When the notification object is fully configured, we schedule it using the UIApplication
method scheduleLocalNotification
(line 25). This finishes the implementation!
Choose Build and Run to compile and start the application on your iPhone or in the iPhone Simulator. After GettingAttention is up and running, click the Alert Me! button. After the initial alert is displayed, click the Home button to exit the application. Go get a drink, and come back in about 4 minutes and 59 seconds. At exactly 5 minutes later, you'll receive a local notification, as shown in Figure 21.4.
Figure 21.4 Local notifications are displayed onscreen even when the application isn't running.
Using Task-Specific Background Processing
So far, we haven't actually done any real background processing! We've suspended an application and generated local notifications, but, in each of these cases, the application hasn't been doing any processing. Let's change that! In our final two examples, we'll execute real code behind the scenes while the application is in the background. Although it is well beyond the scope of this book to generate a VoIP application, we can use our Cupertino application from last hour's lesson, with some minor modifications, to show background processing of location and audio!
Preparing the Cupertino Application for Audio
When we finished off the Cupertino application in the last hour, it told us how far away Cupertino was, and presented straight, left, and right arrows on the screen to indicate the direction the user should be traveling to reach the Mothership. We can update the application to audio using SystemSoundServices
, just as we did in Hour 10's GettingAttention application.
The only tricky thing about our changes is that we won't want to hear a sound repeated if it was the same as the last sound we heard. To handle this requirement, we'll use a constant for each sound: 1 for straight, 2 for right, and 3 for left, and store this in a variable called lastSound
each time a sound is played. We can then use this as a point of comparison to make sure that what we're about to play isn't the same thing we did just play!
Adding the AudioToolbox Framework
To use System Sound Services, we need to first add the AudioToolbox framework. Open the Cupertino (with Compass implementation) project in Xcode. Right-click the Frameworks group and choose Add, Existing Frameworks. Choose AudioToolbox.framework from the list that appears, and then click Add, as shown in Figure 21.5.
Adding the Audio Files
Within the Cupertino Audio Compass - Navigation and Audio folder included with this hour's lesson, you'll find an Audio folder. Drag the files from the audio folder (straight.wav, right.wav, and left.wav) to the Resources group within the Xcode project. Choose to copy the files into the application when prompted, as shown in Figure 21.6.
Figure 21.5 Add the Audio- Toolbox.framework to the project.
Figure 21.6 Add the necessary sound resources to the project.
Updating the CupertinoViewController.h Interface File
Now that the necessary files are added to the project, we need to update the CupertinoViewController interface file. Add an #import
directive to import the AudioToolbox interface file, and then declare instance variables for three SystemSoundID
s (soundStraight
, soundLeft
, and soundRight
) and an integer lastSound
to hold the last sound we played. Remember that these aren't objects, so there's no need to declare the variables as pointers to objects, add properties for them, or release them!
The updated CupertinoViewController.h file should resemble Listing 21.3.
Listing 21.3
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>
#import <AudioToolbox/AudioToolbox.h>
@interface CupertinoViewController : UIViewController
<CLLocationManagerDelegate> {
CLLocationManager *locMan;
CLLocation *recentLocation;
IBOutlet UILabel *distanceLabel;
IBOutlet UIView *distanceView;
IBOutlet UIView *waitView;
IBOutlet UIImageView *directionArrow;
SystemSoundID soundStraight;
SystemSoundID soundRight;
SystemSoundID soundLeft;
int lastSound;
}
@property (assign, nonatomic) CLLocationManager *locMan;
@property (retain, nonatomic) CLLocation *recentLocation;
@property (retain, nonatomic) UILabel *distanceLabel;
@property (retain, nonatomic) UIView *distanceView;
@property (retain, nonatomic) UIView *waitView;
@property (retain, nonatomic) UIView *directionArrow;
-(double)headingToLocation:(CLLocationCoordinate2D)desired
current:(CLLocationCoordinate2D)current;
@end
Adding Sound Constants
To help keep track of which sound we last played, we declared the lastSound
instance variable. Our intention is to use this to hold an integer representing each of our three possible sounds. Rather than remembering that 2 = right, and 3 = left, and so on, let's add some constants to the CupertinoViewController.m implementation file to keep these straight.
Insert these three lines following the existing constants we defined for the project:
#define straight 1
#define right 2
#define left 3
With the setup out of the way, we're ready to implement the code to generate the audio directions for the application.
Implementing the Cupertino Audio Directions
To add sound playback to the Cupertino application, we need to modify two of our existing CupertinoViewController
methods. The viewDidLoad
method will give us a good place to load all three of our sound files and set the soundStraight
, soundRight
, soundLeft
references appropriately. We'll also use it to initialize the lastSound
variable to 0
, which doesn't match any of our sound constants. This ensures that whatever the first sound is, it will play.
Edit CupertinoViewController.m and update viewDidLoad
to match Listing 21.4.
Listing 21.4
- (void)viewDidLoad {
[super viewDidLoad];
NSString *soundFile;
soundFile = [[NSBundle mainBundle]
pathForResource:@"straight" ofType:@"wav"];
AudioServicesCreateSystemSoundID((CFURLRef)
[NSURL fileURLWithPath:soundFile]
,&soundStraight);
[soundFile release];
soundFile = [[NSBundle mainBundle]
pathForResource:@"right" ofType:@"wav"];
AudioServicesCreateSystemSoundID((CFURLRef)
[NSURL fileURLWithPath:soundFile]
,&soundRight);
[soundFile release];
soundFile = [[NSBundle mainBundle]
pathForResource:@"left" ofType:@"wav"];
AudioServicesCreateSystemSoundID((CFURLRef)
[NSURL fileURLWithPath:soundFile]
,&soundLeft);
[soundFile release];
lastSound=0;
locMan = [[CLLocationManager alloc] init];
locMan.delegate = self;
locMan.desiredAccuracy = kCLLocationAccuracyThreeKilometers;
locMan.distanceFilter = 1609;
[locMan startUpdatingLocation];
if ([CLLocationManager headingAvailable]) {
locMan.headingFilter = 15;
[locMan startUpdatingHeading];
}
}
Tip - Remember, this is all code we've used before! If you are having difficulties understanding the sound playback process, refer back to the Hour 10 tutorial.
The final logic that we need to implement is to play each sound when there is a heading update. The CupertinoViewController.m method that implements this is locationManager:didUpdateHeading
. Each time the arrow graphic is updated in this method, we'll prepare to play the corresponding sound with the AudioServicesPlay SystemSound
function. Before we do that, however, we'll check to make sure it isn't the same sound as lastSound
; this will help prevent a Max Headroom stuttering effect as one sound file is played repeatedly over top of itself. If lastSound
doesn't match the current sound, we'll play it and update lastSound
with a new value.
Edit the locationManager:didUpdateHeading
method as described. Your final result should look similar to Listing 21.5.
Listing 21.5
- (void)locationManager:(CLLocationManager *)manager
didUpdateHeading:(CLHeading *)newHeading {
if (self.recentLocation != nil && newHeading.headingAccuracy >= 0) {
CLLocation *cupertino = [[[CLLocation alloc]
initWithLatitude:kCupertinoLatitude
longitude:kCupertinoLongitude] autorelease];
double course = [self headingToLocation:cupertino.coordinate
current:recentLocation.coordinate];
double delta = newHeading.trueHeading - course;
if (abs(delta) <= 10) {
directionArrow.image = [UIImage imageNamed:@"up_arrow.png"];
if (lastSound!=straight) AudioServicesPlaySystemSound(soundStraight);
lastSound=straight;
} else {
if (delta > 180) {
directionArrow.image = [UIImage imageNamed:@"right_arrow.png"];
if (lastSound!=right) AudioServicesPlaySystemSound(soundRight);
lastSound=right;
} else if (delta > 0) {
directionArrow.image = [UIImage imageNamed:@"left_arrow.png"];
if (lastSound!=left) AudioServicesPlaySystemSound(soundLeft);
lastSound=left;
} else if (delta > -180) {
directionArrow.image = [UIImage imageNamed:@"right_arrow.png"];
if (lastSound!=right) AudioServicesPlaySystemSound(soundRight);
lastSound=right;
} else {
directionArrow.image = [UIImage imageNamed:@"left_arrow.png"];
if (lastSound!=left) AudioServicesPlaySystemSound(soundLeft);
lastSound=left;
}
}
directionArrow.hidden = NO;
} else {
directionArrow.hidden = YES;
}
}
The application is now ready for testing. Use Build and Run to install the updated Cupertino application on your iPhone, and then try moving around. As you move, it will speak "Right," "Left," and "Straight" to correspond to the onscreen arrows. Try exiting the applications and see what happens. Surprise! It won't work! That's because we haven't yet updated the project's plist file to contain the Required Background Modes (UIBackgroundModes
) key.
Tip - If, while you're testing the application, it still seems a bit "chatty" (playing the sounds too often), you may want to update the locMan.headingFilter
to a larger value (like 15 or 20) in the viewDidLoad
method. This will help cut down on the number of heading updates.
Adding the Background Modes Key
Our application performs two tasks that should remain active when in a background state. First, it tracks our location. Second, it plays audio to give us a general heading. We need to add both audio and location background mode designations to the application for it to work properly. Update the Cupertino project plist by following these steps:
- Click to open the project's plist file in the resources group (Cupertino-Info.plist).
- Add an additional key to the property list, selecting Required Background Modes (
UIBackgroundModes
) from the Key pop-up menu. - Expand the key and add two values within it: App Plays Audio (Audio) and App Registers for Location Updates (Location), as shown in Figure 21.7. Both values will be selectable from the pop-up menu in the Value field.
- Save the changes to the plist file.
Figure 21.7 Add the background modes that are required by your application.
After updating the plist, install the updated application on your iPhone and try again. This time, when you exit the application, it will continue to run! As you move around, you'll hear spoken directions as Cupertino continues to track your position behind the scenes.
Note - By declaring the location and audio background modes, your application is able to use the full services of Location Manager and the iOS's many audio playback mechanisms when it is in the background.
Completing a Long-Running Background Task
In our final tutorial of the hour, we need to create a project from scratch. Our book isn't about building applications that require a great deal of background processing. Sure, we could demonstrate how to add code to an existing project and allow a method to run in the background, but we don't have any long running methods that could make use of it.
To demonstrate how we can tell iOS to allow something to run in the background, we'll create a new application, SlowCount, that does nothing but count to 1,000—slowly. We'll use the task-completion method of background to make sure that, even when the application is in the background, it continues to count until it reaches 1,000—as shown in Figure 21.8.
Figure 21.8 To simulate a long-running task, our application will count. Slowly.
Preparing the Project
Create a new view-based iPhone application named SlowCount. We'll move through development fairly quickly because, as you can imagine, this application is pretty simple.
The application will have a single outlet, a UILabel
named theCount
, which we'll use to present the counter onscreen. In addition, it will need a an integer to use as a counter (count
), an NSTimer
object that will trigger the counting at a steady interval (theTimer
), and a UIBackgroundTaskIdentifier
variable (not an object!) that we'll use to reference the task we have running in the background (counterTask
).
Note - Every task that you want to enable for background task completion will need its own UIBackgroundTaskIdentifier
. This is used along with the UIApplication
method endBackgroundTask
to identify which background task has just ended.
Open the SlowCountViewController.h file and implement it as shown in Listing 21.6.
Listing 21.6
#import <UIKit/UIKit.h>
@interface SlowCountViewController : UIViewController {
int count;
NSTimer *theTimer;
UIBackgroundTaskIdentifier counterTask;
IBOutlet UILabel *theCount;
}
@property (nonatomic,retain) UILabel *theCount;
@end
Note - The UILabel
theCount
is the only object we'll be accessing and modifying properties of in the application; therefore, it is the only thing that needs a @property
declaration.
Next, clean up after the UILabel
object in the SlowCountViewController.m dealloc
method. The other instance variables either aren't objects (count
, counterTask
) or will be allocated and released elsewhere in the application (NSTimer
):
- (void)dealloc {
[theCount release];
[super dealloc];
}
Creating the User Interface
It's a bit of a stretch to claim that this application has a "user interface," but we still need to prepare the SlowCountViewController.xib to show the theCount
label on the screen.
Open the XIB file in Interface Builder, and drag a UILabel
into the center of the view. Set the label's text to read 0. With the label selected, use the Attributes Inspector (Command+1) to set the label alignment to center and the font size to 36. Finally, align the right and left sides of the label with the right and left sizing guides. You've just created a UI masterpiece, as shown in Figure 21.9.
Finish the view by Control-dragging from the File's Owner icon in the Document window to the UILabel
in the view. Choose theCount
when prompted to make the connection.
Figure 21.9 Add a UILabel to the view to hold the current count.
Implementing the Counter Logic
To finish our applications core functionality (counting!), we need to do two things. First, we need to set the counter (count
) to 0 and allocate and initialize NSTimer
that will fire at a regular interval. Second, when the timer fires, we will ask it to invoke a second method, countUp
. In the countUp
method, we'll check to see whether count
is 1000
. If it is, we'll turn off the timer and we're done, if not, we'll update count
and display it in our UILabel
theCount.
Initializing the Timer and Counter
Let's start with initializing the counter and timer. What better place to do this than in SlowCount.m's viewDidLoad
method. Implement viewDidLoad
as shown in Listing 21.7.
Listing 21.7
1: - (void)viewDidLoad {
2: [super viewDidLoad];
3: count=0;
4: theTimer=[NSTimer scheduledTimerWithTimeInterval:0.1
5: target:self
6: selector:@selector(countUp)
7: userInfo:nil
8: repeats:YES];
9: }
Line 3 initializes our integer counter, count
, to 0.
Lines 4–8 initialize and allocate the theTimer
NSTimer
object with an interval of 0.1
seconds. The selector
is set to use the method countUp
, which we'll be writing next. The timer is set to keep repeating with repeats:YES
.
All that remains is to implement countUp
so that it increments the counter and displays the result.
Updating the Counter and Display
Add the countUp
method, as shown in Listing 21.8, before the viewDidLoad
method in SlowCountViewController.m. This should be quite straightforward—if the count equals 1000
, we're done and it's time to clean up—otherwise, we count!
Listing 21.8
1: - (void)countUp {
2: if (count==1000) {
3: [theTimer invalidate];
4: [theTimer release];
5: } else {
6: count++;
7: NSString *currentCount;
8: currentCount=[[NSString alloc] initWithFormat:@"%d",count];
9: theCount.text=currentCount;
10: [currentCount release];
11: }
12: }
Lines 2–4 handle the case that we've reached the limit of our counting (count==1000
). If it has, we use the timer's invalidate
method to stop it, and then release
it.
Lines 5–11 handle the actual counting and display. Line 5 updates the count
variable. Line 7 declares the currentCount
string, which is then allocated and populated in line 8. Line 9 updates our theCount
label with the currentCount
string. Line 10 releases the string object.
Build and Run the application—it should do exactly what you expect—count slowly until it reaches 1,000. Unfortunately, if you background the application, it will suspend. The counting will cease until the application returns to the foreground.
Enabling the Background Task Processing
To enable the counter to run in the background, we need to mark it as a background task. We'll use this code snippet to mark the beginning of the code we want to execute in the background:
counterTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{
}];
And we'll use this code snippet to mark the end:
[[UIApplication sharedApplication] endBackgroundTask:counterTask];
Note - If we were worried about the application not finishing the background task before it was forced to end (roughly 10 minutes), we could implement the optional code in the beginBackgroundTaskWithExpirationHandler
block. You can always check to see how much time is remaining by checking the UIApplication
property backgroundTimeRemaining
.
Let's update our viewDidLoad
and countUp
methods to include these code additions. In viewDidLoad
, we'll start the background task right before we initialize the counter. In countUp
, we end the background task after count==1000
and the timer is invalidated and released.
Update viewDidLoad
as shown in Listing 21.9.
Listing 21.9
- (void)viewDidLoad {
[super viewDidLoad];
counterTask = [[UIApplication sharedApplication]
beginBackgroundTaskWithExpirationHandler:^{
}];
count=0;
theTimer=[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:@selector(countUp)
userInfo:nil
repeats:YES];
}
Then make the corresponding additions to countUp
, demonstrated in Listing 21.10.
Listing 21.10
- (void)countUp {
if (count==1000) {
[theTimer invalidate];
[theTimer release];
[[UIApplication sharedApplication] endBackgroundTask:counterTask];
} else {
count++;
NSString *currentCount;
currentCount=[[NSString alloc] initWithFormat:@"%d",count];
theCount.text=currentCount;
[currentCount release];
}
}
Save your project files, then Build and Run the application on your iPhone or in the simulator. After the counter starts counting, pressing the Home button to move the application to the background. Wait a minute or so, and then re-open the application through the task manager or the application icon. The counter will have continued to run in the background!
Obviously, this isn't a very compelling project itself, but the implications for what can be achieved in real-world apps is definitely exciting!
Further Exploration
When I sat down to write this lesson, I was torn. Background tasks/multitasking is definitely the "must have" feature of iOS 4.0, but it's a challenge to demonstrate anything meaningful in the span of a dozen or two pages. What I hope we've achieved is a better understanding of how iOS multitasking works and how you might implement it in your own applications. Keep in mind that this is not a comprehensive guide to background processing—there are many more features available, and many ways that you can optimize your background-enabled apps to maximize iPhone battery life and speed.
As a next step, you should read the following sections in Apple's iPhone Application Programming Guide (available through the Xcode documentation): "Executing Code in the Background," "Preparing Your Application to Execute in the Background," and "Initiating Background Tasks."
As you review Apple's documentation, pay close attention to the tasks that your application should be completing as it enters the background. There are implications for games and graphic-intensive applications that are well beyond the scope of what we can discuss here. How well you adhere to these guidelines will determine whether Apple accepts your application or kicks it back to you for optimization.
Summary
Background applications on iOS devices are not the same as background applications on your Macintosh. There are a well-defined set of rules that iOS background-enabled applications you must follow to be considered "good citizens" of iOS 4.x. In this hour's lesson, you learned about the different types of backgrounding available in iOS and the methods available to support background tasks. Over the course of five tutorial applications, you put these techniques to the test, creating everything from notifications triggered when an application isn't running to a simple navigation app that provides background voice prompting.
You should now be well prepared to create your own background-aware apps and take full advantage of the latest and greatest feature of iOS 4.0.
Q&A
- Why can't I run any code I want in the background?
- Someday, I suspect you will, but for now the platform is constrained to the specific types of background processing we discussed. The security and performance implications of running anything and everything on a device that is always connected to the Internet is enormous. Remember that the iPhone is a phone. Apple intends to ensure that when your iPhone needs to be used as a phone, it functions as one!
- What about timeline-based background processing, like IM clients?
- Timeline-based processing (reacting to events that occur over time) is currently not allowed in iOS. This is a disappointment but ensures that there aren't dozens of apps sitting on your phone, eating up resources, waiting for something to happen.
Workshop
Quiz
- Background tasks can be anything you want in iOS 4.0. True or false?
- Any application you compile for iOS 4.0 will continue to run when the user exits it. True or false?
- Only a single long-running background task can be marked background completion. True or false?
Answers
- False. Apple has a well-defined set of rules for implementing background processing in iOS 4.0.
- False. Applications will suspend in the background by default. To continue processing, you must implement background tasks as described in this hour's lesson.
- False. You can mark as many long-running tasks as you'd like, but they must all complete within a set period of time (around 10 minutes).
Activities
- Return to a project in an earlier hour and properly enable it for background processing.