Hour 22. Building Background-Ready Applications


What You’ll Learn in This Hour:

Image How iOS supports background tasks

Image What types of background operations are supported

Image How to disable backgrounding

Image Ways to execute code in the background

Image How to implement background data fetching


“Real multitasking” claims the commercial for a competitor’s tablet. “Unlike Apple, you can run multiple things at once,” chides another ad. As a developer and a fan of iOS, I’ve found these threads amusing in their naiveté and somewhat confusing. iDevices have 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 instead of letting it bog down to the point of being unusable. Rather than an “anything goes” approach, Apple has taken steps to ensure that iOS devices remain responsive at all times.

In recent years, Apple has dramatically opened up background processing with many new capabilities. Unlike the competitors, however, Apple has been cautious in its backgrounding approach—preventing a single process from completely taking over the operating system. In this hour’s lesson, you learn several of the multitasking techniques that you can implement in your applications.

Understanding iOS Backgrounding

As you’ve built the tutorials in this book, you might have noticed that when you quit the applications on your device or in the iOS 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 are background ready as soon as you click the Run button. That doesn’t mean that they will run in the background, just that they’re aware of the 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 and then the application life cycle methods you can tie into.

Types of Backgrounding

We explore five primary types of backgrounding in iOS: application suspension, local notifications, task-specific background processing, task completion, and background fetches.

Suspension

When an application is suspended, it ceases executing code but is 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 are stopped, keeping the app from using up your device’s resources. Any application that you compile 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 Methods” section, later in this hour), but beyond that, it “just works.”

In addition to performing cleanup as an application is being suspended, it is 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, with sounds and app icon badges to appear at a point in time in the future.

Before this can happen, however, you must request permission, using the UIApplication method registerUserNotificationSettings:

let notificationTypes:UIUserNotificationType =
  UIUserNotificationType.Sound | UIUserNotificationType.Badge | UIUserNotificationType.Alert
let notificationSettings =
  UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(notification
Settings)

This generates an alert (during the first execution only) letting the user known that the application will be displaying alerts, even when it isn’t in the foreground.

Once permission has been requested, you can generate a notification. The following code initializes a notification (UILocationNotification), configures it to appear in 5 minutes (300 seconds from “now”), and then uses the application’s scheduleLocalNotification method to complete the scheduling:

let scheduledAlert: UILocalNotification = UILocalNotification()
scheduledAlert.fireDate=NSDate(timeIntervalSinceNow: 300)
scheduledAlert.timeZone=NSTimeZone.defaultTimeZone()
scheduledAlert.alertBody="Hey, remember me?"
UIApplication.sharedApplication().scheduleLocalNotification(scheduledAlert)

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 the View button in the notification window to return to your application.


Note

iOS allows remote notifications to be sent to a device to trigger applications to activate and begin processing. This can be useful for asking an application to retrieve new information when it becomes available (having it instantly appear when the user next opens the app). Using remote notifications requires you to have a server infrastructure set up to track and communicate with your user’s iOS devices (which, unfortunately, is beyond the scope of this book). Learn more by reading the document Local and Push Notification Programming Guide found in the Xcode help system.


Task-Specific Background Processing

Before Apple decided to implement background processing, it did some research on how users worked with their devices. Apple found that people needed specific types of background processing. 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, Voice over IP (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. 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 use is task completion. Using task-completion methods, you can “mark” the tasks in your application that will need to finish before the application 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:

var myLongTask: UIBackgroundTaskIdentifier!

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 =
  UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
     // If you're worried about exceeding 10 minutes, handle it here
  })

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, which is plenty of time for most uses. After the time completes, the application is suspended and treated like any other suspended application.

Background Fetches

The fifth and final multitasking feature we review this hour is background fetches. Using the background fetch feature, your application can periodically launch and execute a method that retrieves and processes update data. Scheduling of the updates happens automatically based on a user’s usage. If a user starts an app and uses it each morning, iOS will make sure that a background fetch occurs before the time the user is typically using it. In addition, iOS prevents multiple applications from attempting background fetches at the same time, thus keeping the device responsive even if background processing is taking place.

As a developer, you need to do just two things to implement background updates. First, you must edit the application:didFinishLaunchingWithOptions method in the application delegate (AppDelegate.swift) to set the minimum amount of time between fetches. This consists of a single line (broken here for readability):

UIApplication.sharedApplication().setMinimumBackgroundFetchInterval(UIApplication
BackgroundFetchIntervalMinimum)

The constant UIApplicationBackgroundFetchIntervalMinimum tells the application that you want updates to happen as often as they can. If you have a specific interval in mind, you can provide a number in seconds instead. You aren’t guaranteed an interval by iOS; it is intended to be scheduled around a user’s activities.

The second step is to implement the background fetch itself. To do this, you add the following method to the application delegate:

func application(application: UIApplication,
    performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {

    //Do something useful here

    //Indicate completion
    completionHandler(UIBackgroundFetchResult.NewData)

}

This method has 30 seconds to execute and perform the background fetch. When completed, it should call completionHandler(UIBackgroundFetchResult.NewData) to indicate success.

Background-Aware Application Life Cycle Methods

In Hour 4, “Inside Cocoa Touch,” you started learning about the application life cycle, shown in Figure 22.1. You learned that 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.

Image

FIGURE 22.1 The iOS application life cycle.

In addition to applicationDidEnterBackground, you should implement several other methods to be a proper background-aware iOS citizen. For many small applications, you do not need to do anything with these other than leave them as is in the application delegate. As your projects increase in complexity, however, 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: Your Application Can Terminate at Any Time

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.



Note

If you’re interested in implementing background fetching, you can take advantage of the methods we discuss here, but they aren’t necessary. You’re only required to set the minimum fetch time and then to define a method for handling the data fetching itself. We’ll implement some simple background fetching later this hour.


Apple expects to see the following methods in your background-aware apps:

Image application:didFinishLaunchingWithOptions: Called when your application first launches. If your application is terminated while suspended or purged from memory, it needs to restore its previous state manually. (You did save it to your user’s preferences, right?)

Image 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.

Image 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.

Image applicationDidEnterBackground: Called when the application has become a background application. This largely replaces applicationWillTerminate, which was used when an application quit. 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.

Image applicationWillEnterForeground: Called when an application returns to an active state after being backgrounded.

Image 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 application delegate file. 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.

Now that understand the background-related methods and types of background processing available to you, let’s look at how you can implement them. To do this, we reuse tutorials that we’ve built throughout the book (with two exceptions). We do not cover 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, many different “diversion” apps 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 were to start from scratch each time you ran it. To implement this change in the project, follow these steps:

1. Open the project in which you want to disable backgrounding (such as HelloNoun).

2. Choose the main project group and click the HelloNoun target, and then expand the Custom iOS Target Properties under the Info tab. Or open the project’s plist file in the Supporting Files group (Info.plist).

3. Add an additional row to the displayed property list (right-click the list, choose Add Row), selecting Application Does Not Run in Background (UIApplicationExitsOnSuspend) from the Key pop-up menu.

4. Choose Yes from the pop-up menu at the right side of the Value column, as shown in Figure 22.2.

Image

FIGURE 22.2 Add the Application Does Not Run in Background (UIApplicationExitsOnSuspend) key to the project.


Note

By default, the plist editor shows the “developer friendly” names for plist entries. To see the underlying keys/values, right-click on the list and choose Show Raw Keys/Values from the menu.


Run the application on your device or in the iOS Simulator. When you exit the application with the Home button, it will not be suspended, 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 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, Sliders, and Steppers.” 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 edit the application delegate method applicationWillEnterForeground. Recall that this method is invoked only when an application is returning from a backgrounded state. Open AppDelegate.swift and implement the method, as shown in Listing 22.1.

LISTING 22.1 Implementing the applicationWillEnterForeground Method


 1: func applicationWillEnterForeground(application: UIApplication) {
 2:     let alertController = UIAlertController(title: "Yawn!",
 3:         message: "Was I asleep?",
 4:         preferredStyle: UIAlertControllerStyle.Alert)
 5:
 6:     let defaultAction = UIAlertAction(title: "Welcome Back",
 7:         style: UIAlertActionStyle.Cancel,
 8:         handler: nil)
 9:
10:     alertController.addAction(defaultAction)
11:     self.window!.rootViewController!.presentViewController(alertController,
12:         animated: true, completion: nil)
13: }


Within the method, we declare, configure, and show an alert controller, exactly as we did in the Getting Attention tutorial in Hour 10, “Getting the User’s Attention.” After updating the code, 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 with Xcode’s 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 22.3.

Image

FIGURE 22.3 The applicationWillEnterForeground 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 need beyond those few lines. To demonstrate the use of local notifications, we update Hour 10’s doAlert method. Instead of just displaying an alert, it also shows a notification 5 minutes later and then schedules local notifications to occur every day thereafter.

Requesting Authorization for Notifications

Before displaying a notification, your application must request authorization. Without proper permission, notifications will fail silently (that is, your code will run, but nothing will be displayed).

Open the GettingAttention application and edit AppDelegate.swift file, updating the application:didFinishLaunchingWithOptions method, as shown in Listing 22.2.

LISTING 22.2 Requesting Notification Authorization


 1: func application(application: UIApplication,
 2:   didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
 3:   let notificationTypes:UIUserNotificationType =
 4:       UIUserNotificationType.Sound |
 5:           UIUserNotificationType.Badge |
 6:           UIUserNotificationType.Alert
 7:   let notificationSettings =
 8:       UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)
 9:   UIApplication.sharedApplication().registerUserNotificationSettings(notification
Settings)
10:
11:   return true12: }


To request authorization to use notifications, you must first define the types of notifications (UIUserNotificationType) you want to display (lines 3–6). In this example, we’re requesting the ability to use any type of notification (sounds, alerts, and badges)—so all the bases are covered. Next, you must create a UIUserNotificationSettings object that includes the types (lines 7–8). Finally, the settings are registered with the application (line 9), which generates an alert and prompts the user for permission.

What’s the takeaway here? Use this code block and you’ll properly request the ability to use any local notifications. Let’s move on to the notifications themselves.

Common Notification Properties

You want to configure several properties when creating notifications. A few of the more interesting of these include the following:

Image applicationIconBadgeNumber: An integer that is displayed on the application icon when the notification is triggered.

Image fireDate: An NSDate object that provides a time in the future for the notification to be triggered.

Image timeZone: The time zone to use for scheduling the notification.

Image repeatInterval: How frequently, if ever, the notification should be repeated.

Image soundName: A string (NSString) containing the name of a sound resource to play when the notification is triggered.

Image alertBody: 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 22.3. (Bolded lines are additions to the existing method.) Once the code is in place, we walk through it together.

LISTING 22.3 Updating doAlert to Register a Local Notification


 1: @IBAction func doAlert(sender: AnyObject) {
 2:     let scheduledAlert: UILocalNotification = UILocalNotification()
 3:
 4:     UIApplication.sharedApplication().cancelAllLocalNotifications()
 5:     scheduledAlert.applicationIconBadgeNumber=1
 6:     scheduledAlert.fireDate=NSDate(timeIntervalSinceNow: 300)
 7:     scheduledAlert.timeZone=NSTimeZone.defaultTimeZone()
 8:     scheduledAlert.repeatInterval=NSCalendarUnit.CalendarUnitDay
 9:     scheduledAlert.soundName="soundeffect.wav"
10:     scheduledAlert.alertBody="I'd like to get your attention again!"
11:     UIApplication.sharedApplication().scheduleLocalNotification(scheduledAlert)
12:
13:     let alertController = UIAlertController(title: "Alert Me Button Selected",
14:         message: "I need your attention NOW!",
15:         preferredStyle: UIAlertControllerStyle.Alert)
16:
17:     let defaultAction = UIAlertAction(title: "Ok",
18:         style: UIAlertActionStyle.Cancel,
19:         handler: nil)
20:
21:     alertController.addAction(defaultAction)
22:     presentViewController(alertController, animated: true, completion: nil)
23: }


First, in line 2, we create 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 4, 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.

In line 5, we configure the notification’s applicationIconBadgeNumber variable 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 6 uses the fireDate along with the NSDate class method DateWithTimeIntervalSinceNow to set the notification to be triggered 300 seconds in the future.

Line 7 sets the timeZone for the notification. This should almost always be set to the local time zone, as returned by NSTimeZone.defaultTimeZone().

Line 8 sets the repeatInterval variable property for the notification. This can be chosen from a variety of constants, such as NSCalendarUnit.CalendarUnitDay (daily), NSCalendarUnit.CalendarUnitHour (hourly), and NSCalendarUnit.CalendarUnitMinute (every minute). You can find the full list in the NSCalendar class reference in the Xcode developer documentation.

In Line 9, we set a sound to be played along with the notification. The soundName variable property is configured with a string with the name of a sound resource. Because we already have soundeffect.wav available in the project, we can use that without further additions.

Line 10 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 (lines 11). This finishes the implementation.

Choose Run to compile and start the application on your device or in the iOS 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 22.4.

Image

FIGURE 22.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 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 the preceding 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 preceding 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 define a variable, lastSound, which is set to the last sound that has been 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. Make a copy of the Cupertino compass application and name the folder Cupertino Audio Compass. Then open the Cupertino project in Xcode. Select the ViewController.swift file and add an import line for AudioToolbox after the existing import of the Core Location framework:

import AudioToolbox

Adding the Audio Files

Within the project folder included with this hour’s lesson, you’ll find an Audio folder containing simple direction sounds: straight.wav, right.wav, and left.wav. Drag the audio folder to the main project code group within the Xcode project. Choose to copy the files and create groups when prompted.

Adding System Sound ID Variables

Next, we need to update the code following the class line in ViewController.swift to declare four new variable properties for three SystemSoundIDs referencing our sound files (soundStraight, soundLeft, and soundRight) and a fourth (lastSound) to hold the last SystemSoundID we played. Remember that sound IDs aren’t objects, they’re just integers; so, we’ll initialize all four sound IDs to 0, giving us a clean starting point:

var soundStraight: SystemSoundID = 0
var soundRight: SystemSoundID = 0
var soundLeft: SystemSoundID = 0
var lastSound: SystemSoundID = 0

The setup is complete. We’re now 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 ViewController methods. The viewDidLoad method will give us a good place to load all three of our sound files and set the soundStraight, soundRight, and soundLeft references appropriately.

Edit ViewController.swift and update the second half of viewDidLoad to match Listing 22.4.

LISTING 22.4 Initializing the Sound File References in viewDidLoad


 1: override func viewDidLoad() {
 2:     super.viewDidLoad()
 3:     // Do any additional setup after loading the view, typically from a nib.
 4:     locMan.delegate = self
 5:     locMan.desiredAccuracy = kCLLocationAccuracyThreeKilometers
 6:     locMan.distanceFilter = 1609; // a mile
 7:     locMan.requestWhenInUseAuthorization()
 8:     locMan.startUpdatingLocation()
 9:
10:     if CLLocationManager.headingAvailable() {
11:         locMan.headingFilter = 10 // 10 degrees
12:         locMan.startUpdatingHeading()
13:     }
14:
15:     var soundFile:String!
16:
17:     soundFile = NSBundle.mainBundle().pathForResource("straight", ofType: "wav")!
18:     AudioServicesCreateSystemSoundID(NSURL(fileURLWithPath: soundFile),&soundStraight)
19:
20:     soundFile = NSBundle.mainBundle().pathForResource("right", ofType: "wav")!
21:     AudioServicesCreateSystemSoundID(NSURL(fileURLWithPath: soundFile),&soundRight)
22:
23:     soundFile = NSBundle.mainBundle().pathForResource("left", ofType: "wav")!
24:     AudioServicesCreateSystemSoundID(NSURL(fileURLWithPath: soundFile),&soundLeft)
25: }



Tip

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 ViewController.swift method that implements this is locationManager:didUpdateHeading. Each time the arrow graphic is updated in this method, we prepare to play the corresponding sound with the AudioServicesPlaySystemSound function. Before we do that, however, we check to make sure that it isn’t the same sound as lastSound; this helps 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 play it and update lastSound with a new value. For the left arrow, for example, we might use this code fragment to play the sound and set the lastSound variable property:

if lastSound != soundLeft {
    AudioServicesPlaySystemSound(soundLeft)
    lastSound=soundLeft
}

Edit the locationManager:didUpdateHeading method as described. Your final result should look similar to Listing 22.5.

LISTING 22.5 Adding Audio Feedback When the Heading Updates


 1: func locationManager(manager: CLLocationManager!,
 2:     didUpdateHeading newHeading: CLHeading!) {
 3:     if (recentLocation != nil && newHeading.headingAccuracy >= 0) {
 4:         let cupertino:CLLocation = CLLocation(latitude: kCupertinoLatitude,
 5:             longitude: kCupertinoLongitude)
 6:         let course: Double = headingToLocation(cupertino.coordinate,
 7:             current:recentLocation.coordinate)
 8:         let delta: Double = newHeading.trueHeading - course
 9:         if (abs(delta) <= 10) {
10:             directionArrow.image = UIImage(named: "up_arrow.png")
11:             if lastSound != soundStraight {
12:                 AudioServicesPlaySystemSound(soundStraight)
13:                 lastSound=soundStraight
14:             }
15:         } else {
16:             if (delta > 180) {
17:                 directionArrow.image = UIImage(named: "right_arrow.png")
18:                 if lastSound != soundRight {
19:                     AudioServicesPlaySystemSound(soundRight)
20:                     lastSound=soundRight
21:                 }
22:             }
23:             else if (delta > 0) {
24:                 directionArrow.image = UIImage(named: "left_arrow.png")
25:                 if lastSound != soundLeft {
26:                     AudioServicesPlaySystemSound(soundLeft)
27:                     lastSound=soundLeft
28:                 }
29:             }
30:             else if (delta > -180) {
31:                 directionArrow.image = UIImage(named: "right_arrow.png")
32:                 if lastSound != soundRight {
33:                     AudioServicesPlaySystemSound(soundRight)
34:                     lastSound=soundRight
35:                 }
36:             }
37:             else {
38:                 directionArrow.image = UIImage(named: "left_arrow.png")
39:                 if lastSound != soundLeft {
40:                     AudioServicesPlaySystemSound(soundLeft)
41:                     lastSound=soundLeft
42:                 }
43:             }
44:         }
45:         directionArrow.hidden = false
46:     } else {
47:         directionArrow.hidden = true
48:     }
49: }


The application is now ready for testing. Click Run to install the updated Cupertino application on your device, 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 to allow background processing.


Tip

If you’re testing the application and it still seems a bit “chatty” (playing the sounds too often), you might want to update 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

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 capabilities to the application for it to work properly. Update the Cupertino project by following these steps:

1. Choose the main project group and click the Cupertino target, and then expand the Background Modes section under the Capabilities tab.

2. Click the switch to turn on Background Modes.

3. Check both the Audio and Airplay and the Location Updates check boxes, as shown in Figure 22.5.

Image

FIGURE 22.5 Add the background modes required by your application.

After updating the capabilities, install the updated application on your device 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.

By declaring the location and audio background modes, your application is able to use the full services of Location Manager and iOS’s many audio playback mechanisms when it is in the background.

Completing a Long-Running Background Task

In our next 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, so we need to be creative to demonstrate this feature. Sure, we could add code to an existing project that would allow a method to run in the background, but we don’t have any long-running methods that could make use of it.

Implementation Overview

To demonstrate how we can tell iOS to allow something to run in the background, we create a new application, SlowCount, that does nothing but count to 1,000—slowly. We 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 22.6).

Image

FIGURE 22.6 To simulate a long-running task, our application will count slowly.

Setting Up the Project

Create a new single-view application named SlowCount. We move through development fairly quickly because, as you can imagine, this application is pretty simple.

Planning the Variables and Connections

The application has a single outlet, a UILabel named theCount, which we use to present the counter onscreen. In addition, it needs several variable properties: an integer to use as a counter (count), an NSTimer object that triggers the counting at a steady interval (theTimer), and a UIBackgroundTaskIdentifier variable (not an object) that we use to reference the task we have running in the background (counterTask).


Note

Every task that you want to enable for background task completion needs its own UIBackgroundTaskIdentifier. This is used along with the UIApplication method endBackgroundTask to identify which background task has just ended.


Designing the Interface

It’s a bit of a stretch to claim that this application has a user interface (UI), but we still need to prepare Main.storyboard to show the theCount label on the screen.

Open the initial scene, and drag a label (UILabel) into the center of the view. Set the label’s text to read 0. With the label selected, use the Attributes Inspector (Option-Command-4) to set the label alignment to center and the font size to something a bit bigger. 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 22.7.

Image

FIGURE 22.7 Add a UILabel to the view to hold the current count.

Creating and Connecting the Outlet

We’ve got one UI object to connect to a single outlet. Switch to the assistant editor, and then Control-drag from the label to below the class line in ViewController.swift. Name the outlet theCount when prompted to make the connection.

Implementing the Application Logic

To finish our application’s core functionality (counting), we need to declare and deal with the additional variable properties: the counter (count), the NSTimer object to give us a nice delay while counting (theTimer), and a UIBackgroundTaskIdentifier to track the task (counterTask). In addition, we implement a method that does the counting (and nothing else) called countUp.

Update the ViewController.swift file after the existing @IBOutlet to declare the variable properties:

var count: Int = 0
var counterTask: UIBackgroundTaskIdentifier!var theTimer: NSTimer!

With most of the prep work done, we have two more things left to complete. First, we need to initialize the NSTimer to fire at a regular interval. Second, when the timer fires, we ask it to invoke a second method, countUp. In the countUp method, we check to see whether count is 1000. If it is, we turn off the timer and we’re done; if not, we update count and display it in our UILabel theCount.

Initializing the Timer and Counter

Let’s start with initializing the timer. What better place to do this than in the viewDidLoad method? Implement viewDidLoad, as shown in Listing 22.6.

LISTING 22.6 Scheduling a Timer When the Application Starts


1: override func viewDidLoad() {
2:     super.viewDidLoad()
3:
4:     theTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self,
5:                     selector: "countUp", userInfo: nil, repeats: true)
6: }


Lines 4–5 initialize the theTimer NSTimer object with an interval of 0.1 seconds. The selector is set to use the method countUp, which we write next. The timer is set to keep repeating with repeats:true.

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 22.7, in ViewController.swift. This should be quite straightforward: If the count equals 1000, we’re done and it’s time to clean up (we can disable the timer); otherwise, we count.

LISTING 22.7 Updating the Counter


1: func countUp() {
2:     if count==1000 {
3:         theTimer.invalidate()
4:     } else {
5:         count++
6:         theCount.text="(count)"
7:     }
8: }


Lines 2–4 handle the case where we’ve reached the limit of our counting (count==1000). When that occurs, we use the timer’s invalidate method to stop it because it isn’t needed anymore (line 3).

Lines 4–7 handle the actual counting and display. Line 5 updates the count variable property. Line 6 updates our theCount label with the contents of count.

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 use this code snippet to mark the beginning of the code we want to execute in the background:

counterTask =
  UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
        // If you're worried about exceeding 10 minutes, handle it here
  })

And we use this code snippet to mark the end:

UIApplication.sharedApplication().endBackgroundTask(counterTask)


Note

If you are worried about the application not finishing the background task before it is forced to end (roughly 10 minutes), you could implement the optional code in the beginBackgroundTaskWithExpirationHandler closure. You can always check to see how much time is remaining by checking the UIApplication variable property backgroundTimeRemaining.


Let’s update our viewDidLoad and countUp methods to include these code additions. In viewDidLoad, we 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.

Update viewDidLoad, as shown in Listing 22.8 (lines 4–7).

LISTING 22.8 Setting the Start of Background Processing


 1: override func viewDidLoad() {
 2:  super.viewDidLoad()
 3:  // Do any additional setup after loading the view, typically from a nib.
 4:  counterTask =
 5:   UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
 6:   // If you're worried about exceeding 10 minutes, handle it here
 7:   })
 8:  theTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self,
 9:                  selector: "countUp", userInfo: nil, repeats: true)
10:
11: }


Then make the corresponding additions to countUp (line 4), as demonstrated in Listing 22.9.

LISTING 22.9 Setting the End of Background Processing


1: func countUp() {
2:     if count==1000 {
3:         theTimer.invalidate()
4:         UIApplication.sharedApplication().endBackgroundTask(counterTask)
5:     } else {
6:         count++
7:         theCount.text="(count)"
8:     }
9: }


That’s all it takes. Your project should now be able to run in the background.

Building the Application

Run the application on your device or in the Simulator. After the counter starts counting, press the Home button to move the application to the background. Wait a minute or so, and then reopen 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.

Performing a Background Fetch

The final tutorial of this hour is, in your author’s opinion, one of the most interesting: implementing a background fetch. If you recall from the introduction, this is the ability of an application to periodically activate and retrieve information without the user needing to do a thing. We’ll make this happen in the final project (and with just a surprisingly small amount of code).

Implementation Overview

The purpose of our new application, BackgroundDownload, is to download and display a new background image using a background fetch. Depending on how often a user uses the application, he’ll see a new image each time it starts (without having to wait for it to download). There isn’t a UI to speak of, beyond the UIImageView used as the background, as shown in Figure 22.8.

Image

FIGURE 22.8 The background fetch will periodically download a new image, even when the application isn’t running.

Setting Up the Project

As always, create a new single-view application named BackgroundDownload. This application is even simpler than the last, so we’ll be finished before you know it.

And we’re done.

Not really.

Planning the Variables and Connections

BackgroundDownload will have a single outlet, a UIImageView named backgroundImage, which will contain the background image.

Designing the Interface

Open the Main.Storyboard file and drag a UIImageView into the initial scene’s view. Size it to cover the entire view, and then open the Attributes Inspector and set the image view’s View mode to Aspect Fill, as shown in Figure 22.9.

Image

FIGURE 22.9 Configure the image view to be Aspect Fill.

Creating and Connecting the Outlet

Like the last project, there’s a single UI element (the UIImageView) that we need to connect to an outlet (backgroundImage). Switch to the assistant editor, and be sure to select the ViewController.swift file on the right.

Control-drag from the UIImageView to below the class line in ViewController.swift. Name the outlet backgroundImage when prompted to make the connection.

Implementing the Application Logic

To add the background fetching functionality, we will be making some edits in the AppDelegate class. First, we’ll define the minimum frequency with which the application requests background fetches. This consists of a single line within application:didFinishLaunchingWithOptions. Open AppDelegate.swift and edit the method, as shown in Listing 22.10.

LISTING 22.10 Defining the Minimum Background Fetch Interval


func application(application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
UIApplication.sharedApplication().setMinimumBackgroundFetchInterval(UIApplication BackgroundFetchIntervalMinimum)
    return true
}


The method setMinimumBackgroundFetchInterval defines the minimum amount of time between fetches. Rather than setting an exact time, we use the constant UIApplicationBackgroundFetchIntervalMinimum to tell iOS that we want the fetch to occur as often as possible.

Now that the application knows we want to perform background updates, we must implement the method application:performFetchWithCompletionHandler. The method will retrieve an image from a website, then use it to set the image variable property of the backgroundImage ViewController variable property.

Implement the application:performFetchWithCompletionHandler using Listing 22.11 as your guide. We’ll go through the details afterward.

LISTING 22.11 Finishing the Background Fetch by Implementing application:performFetchWithCompletionHandler


 1: func application(application: UIApplication,
 2:   performFetchWithCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
 3:
 4:   let url: NSURL = NSURL(string: "http://www.floraphotographs.com/randomimage.php")!
 5:   let data: NSData = NSData(contentsOfURL: url)!
 6:   let imageData: UIImage = UIImage(data: data)!
 7:   let myViewController: ViewController = self.window!.rootViewController! as ViewController
 8:   myViewController.backgroundImage.image = imageData
 9:
10:   //Indicate completion
11:   completionHandler(UIBackgroundFetchResult.NewData)
12:
13: }


Line 4 allocates an NSURL object with a web address that will return a random image from Floraphotographs.com. Line 5 reads the data returned by the URL into an NSData object named data. In Line 6, that data is transformed into a UIImage using the UIImage convenience method imageWithData. At this point, we’ve performed the background fetch; now we just need to do something with the image we’ve downloaded!

In Line 7, we take advantage of the fact that the window object defined in the application delegate has a reference to the active view controller through the variable property rootViewController. This is cast as an instance of our ViewController class and stored in myViewController so that it’s easier to access.

Line 8 accesses the backgroundImage variable property of ViewController and sets image to the image we downloaded.

Line 11 is a required function call to completionHandler tell iOS that we’ve successfully completed a background fetch operation. Depending on the outcome, we might also pass the constant UIBackgroundFetchResult.Failed (to indicate a failure) or UIBackgroundFetchResult.NoData (if nothing has changed).

Adding the Background Fetch Mode

Only one thing remains before we can give this a try: setting the Background Fetch background mode. Update the BackgroundDownload project by completing these steps:

1. Choose the main project group and click the BackgroundDownload target, and then expand the Background Modes section under the Capabilities tab.

2. Click the switch to turn on Background Modes.

3. Check the Background Fetch check box, as shown in Figure 22.10.

Image

FIGURE 22.10 Add the Background Fetch mode to your application.

Building the Application

Go ahead and run the finished BackgroundDownload application in the iOS Simulator. You’ll notice that it does absolutely nothing! The reason you won’t see a background image is because we’ve only implemented fetching of an image when the application is in the background. You can either move the app to the background and wait, or you can choose Debug, Simulate Background Fetch from the Debug menu in Xcode. This automatically exits the app and forces it to perform a fetch. The next time you start it, the background image will be set.

Further Exploration

When I sat down to write this lesson, I was torn. Background tasks/multitasking is definitely the “must have” feature of iOS, 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 battery life and speed.

As a next step, you should read the sections “App States and Multitasking” and “Background Execution and Multitasking” in Apple’s iOS Application Programming Guide (available through the Xcode documentation).

As you review Apple’s documentation, pay close attention to the tasks that your application should be completing as it works in 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 well-defined rules that background-enabled applications must follow to be considered “good citizens” of iOS. In this hour’s lesson, you learned about the different types of backgrounding available and the methods available to support background tasks. Over the course of six tutorial applications, you put these techniques to the test, creating everything from notifications triggered when an application isn’t running to a navigation app with background voice prompting and an application that automatically updates its content when it isn’t actively running.

You should now be well prepared to create your own background-aware apps and take full advantage of the powerful hardware in your iPhone, iPad, or iPod.

Q&A

Q. Why can’t I run any code I want in the background?

A. 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 are enormous. Apple intends to ensure that your device remains operational in any conditions, unlike the competitors, where anything goes.

Q. If my application handles background operations, do I need to worry about iOS forcing my app to quit?

A. Absolutely. The currently executing application (in the foreground) always has priority. Your application should be prepared to exit at any time if resources are constrained.

Workshop

Quiz

1. To start a long running task, which of the following methods would you use?

a. backgroundTaskWithExpirationHandler

b. startBackgroundTaskWithExpirationHandler

c. beginBackgroundTaskWithExpirationHandler

d. begin]TaskWithExpirationHandler

2. To clear any pending local notifications for your app, you should use which method?

a. cancelNotifications

b. disableAllLocalNotifications

c. removeAllLocalNotifications

d. cancelAllLocalNotifications

3. Playing audio is an example of what type of backgrounding?

a. Task-specific

b. Forbidden

c. Automatic

d. Time-limited

4. Before scheduling a local notification, you must first ask for what?

a. Memory

b. Authorization

c. Network resources

d. Data storage

5. A background fetch has how many seconds to complete?

a. 30

b. 10

c. 60

d. 120

6. Which method enables you to stop an NSTimer when you are finished with it?

a. close

b. finish

c. delete

d. invalidate

7. To enable background processing of GPS data, you use which capability check box?

a. Location Services

b. Location Updates

c. GPS Updates

d. User Data Updates

8. A local notification can update an application’s icon badge using which variable property?

a. badgeNumber

b. badgeValue

c. applicationIconBadgeNumber

d. setBadgeIconValue

9. To request the frequency of background fetches, you must use which method?

a. setMinimumBackgroundFetchInterval

b. setMaximumBackgroundFetchInterval

c. setMinimumInterval

d. setMaximumInterval

10. To set the message displayed in a local notification, you can use which variable property?

a. alertText

b. alertInfo

c. alertBody

d. alertDisplay

Answers

1. C. Long-running tasks are started by including a call to the beginBackgroundTaskWithExpirationHandler method.

2. D. Use the cancelAllLocalNotifications to clear any existing notifications that your application may have generated.

3. A. Background audio is an example of task-specific backgrounding.

4. B. You must request authorization from the user before attempting to use local notifications.

5. A. Background fetches must complete within 30 seconds; otherwise, iOS will terminate them automatically.

6. D. The invalidate method is used to stop an NSTimer when you are finished using it.

7. B. The Location Updates check box must be checked in order for your application to receive and process changes in the user’s location while in the background.

8. C. Local notifications can manipulate the badge on an application’s icon by setting the applicationIconBadgeNumber variable property.

9. A. You can only set the minimum amount of time between background fetches; this is managed through the setMinimumBackgroundFetchInterval variable property.

10. C. The alertBody is used to set the message contents for a local notification.

Activities

1. Return to a project in an earlier hour and properly enable it for background processing.

2. Test to see what happens when an application with background processing attempts to run longer than 10 minutes. Is it suspended? Terminated? How do you recover from this if your application does not finish its task?

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

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