© Molly Maskrey, Kim Topley, David Mark, Fredrik Olsson and Jeff Lamarche 2016

Molly Maskrey, Kim Topley, David Mark, Fredrik Olsson and JEFF LAMARCHE, Beginning iPhone Development with Swift 3, 10.1007/978-1-4842-2223-2_15

15. Multithreaded Programming Using Grand Central Dispatch

Molly Maskrey, Kim Topley2, David Mark3, Fredrik Olsson4 and Jeff Lamarche5

(1)Parker, Colorado, USA

(2)Apt 10G, New York, USA

(3)Arlington, Virginia, USA

(4)Stockholm, Sweden

(5)New Hartford, New York, USA

While the idea of programming multithreaded functions in any environment may seem daunting at first (see Figure 15-1), Apple came up with a new approach that makes multithreaded programming much easier. Grand Central Dispatchcomprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in iOS and macOS.

A329781_3_En_15_Fig1_HTML.jpg
Figure 15-1. Programming multithreaded applications can seem to be a disheartening experience

A big challenge facing developers today is writing software able to perform complex actions in response to user input while remaining responsive, so that the user isn’t constantly kept waiting while the processor does some behind-the-scenes task. That challenge has been with us all along; and in spite of the advances in computing technology that bring us faster CPUs, the problem persists. Look at the nearest computer screen; chances are that the last time you sat down to work at your computer, at some point, your work flow was interrupted by a spinning mouse cursor of some kind or another.

One of the reasons this has become so problematic is the way software is typically written: as a sequence of events to be performed sequentially. Such software can scale up as CPU speeds increase, but only to a certain point. As soon as the program gets stuck waiting for an external resource, such as a file or a network connection, the entire sequence of events is effectively paused. All modern operating systems now allow the use of multiple threads of execution within a program, so that even if a single thread is stuck waiting for a specific event, the other threads can keep going. Even so, many developers see multithreaded programming as a mystery and shy away from it.

Note

A thread is a sequence of instructions managed independently by the operating system.

Apple provides Grand Central Dispatch (GCD) giving the developer an entirely new API for splitting up the work the application needs to do into smaller chunks that can be spread across multiple threads and, with the right hardware, multiple CPUs.

We access this API using Swift closures , providing a convenient way to structure interactions between different objects while keeping related code closer together in our methods.

Creating the SlowWorker Application

As a platform for demonstrating how GCD works, we’ll create the SlowWorker application that consists of a simple interface driven by a single button and a text view. Click the button, and a synchronous task is immediately started, locking up the app for about ten seconds. Once the task completes, some text appears in the text view, as shown in Figure 15-2.

A329781_3_En_15_Fig2_HTML.jpg
Figure 15-2. The SlowWorker application hides its interface behind a single button. Click the button, and the interface hangs for about ten seconds while the application does its work

Start by using the Single View Application template to make a new application in Xcode, as you’ve done many times before. Name this one SlowWorker, set Devices to Universal, click Next to save your project, and so on. Next, make the changes to ViewController.swift, as shown in Listing 15-1.

Listing 15-1. Add These Methods to the ViewController.swift File
    @IBOutlet var startButton: UIButton!
    @IBOutlet var resultsTextView: UITextView!


    func fetchSomethingFromServer() -> String {
        Thread.sleep(forTimeInterval: 1)
        return "Hi there"
    }


    func processData(_ data: String) -> String {
        Thread.sleep(forTimeInterval: 2)
        return data.uppercased()
    }


    func calculateFirstResult(_ data: String) -> String {
        Thread.sleep(forTimeInterval: 3)
        return "Number of chars: (data.characters.count)"
    }


    func calculateSecondResult(_ data: String) -> String {
        Thread.sleep(forTimeInterval: 4)
        return data.replacingOccurrences(of: "E", with: "e")
    }


    @IBAction func doWork(_ sender: AnyObject) {
            let startTime = NSDate()
            self.resultsTextView.text = ""
            let fetchedData = self.fetchSomethingFromServer()
            let processedData = self.processData(fetchedData)
            let firstResult = self.calculateFirstResult(processedData)
            let secondResult = self.calculateSecondResult(processedData)
            let resultsSummary =
                "First: [(firstResult)] Second: [(secondResult)]"
            self.resultsTextView.text = resultsSummary
            let endTime = NSDate()
            print("Completed in (endTime.timeIntervalSince(startTime as Date)) seconds")
    }

As you can see, the work of this cl ass (such as it is) is split up into a number of small pieces. This code simulates some slow activities, and none of those methods really do anything time-consuming at all. To make things interesting, each method contains a call to the sleep(forTimeInterval: ) class method in Thread, which simply makes the program (specifically, the thread from which the method is called) effectively pause and do nothing at all for the given number of seconds. The doWork() method also contains code at the beginning and end to calculate the amount of time it took for all the work to be done.

Now open Main.storyboard and drag a Button and a Text View into the empty View window. Position the controls as shown in Figure 15-3. You’ll see some default text. Clear the text in the Text View and change the button’s title to Start Working. To set the auto layout constraints, start by selecting the Start Working button, and then click the Align button at the bottom right of the editor area. In the pop-up, check Horizontally in Container and Click Add 1 Constraint. Next, Control-drag from the button to the top of the View window, release the mouse and select Vertical Spacing to Top Layout Guide. To complete the constraints for this button, Control-drag from the button down to the text view, release the mouse, and select Vertical Spacing. To fix the position and size of the text view, expand the View Controller Scene in the Document Outline and Control-drag from the text view in the storyboard to the View icon in the Document Outline. Release the mouse and, when the pop-up appears, hold down the Shift key and select Leading Space to Container Margin, Trailing Space to Container Margin, and Vertical Spacing to Bottom Layout Guide, and then click return to apply the constraints. That completes the auto layout constraints for this application.

A329781_3_En_15_Fig3_HTML.jpg
Figure 15-3. The SlowWorker interface consists of a button and a text view

Control-drag from View Controller icon in the Document Outline to connect the view controller’s two outlets (i.e., the startButton and resultsTextView instance variables) to the button and the text view.

Next, Control-drag from the button to the View Controller, release the mouse and select the doWork() method in the pop-up so that it’s called when the button is pressed. Finally, select the text view, use the Attributes Inspector to uncheck the Editable check box (it’s in the upper-right corner), and delete the default text from the text view.

Save your work, and then select Run. Your app should start up, and pressing the button will make it work for about ten seconds (the sum of all those sleep amounts) before showing you the results. During your wait, you'll see that the Start Working button fades visibly, never turning back to its normal color until the “work” is done. Also, until the work is complete, the application’s view is unresponsive. Tapping anywhere on the screen or rotating the device has no effect. In fact, the only way you can interact with your application during this time is by tapping the home button to switch away from it. This is exactly the state of affairs we want to avoid.

Threading Basics

Before we start implementing solutions, let’s go over some concurrency basics. This is far from a complete description of threading in iOS or threading in general. I just want to explain enough for you to understand what we’re doing in this chapter. Most modern operating systems (including, of course, iOS) support the notion of threads of execution. Each process can contain multiple threads, which all run concurrently. If there’s just one processor core, the operating system will switch between all executing threads, much like it switches between all executing processes. If more than one core is available, the threads will be distributed among them, just as processes are.

All threads in a process share the same executable program code and the same global data. Each thread can also have some data that is exclusive to the thread. Threads can make use of a special structure called a mutex (short for mutual exclusion) or a lock, which can ensure that a particular chunk of code can’t be run by multiple threads at once. This is useful for ensuring correct outcomes when multiple threads access the same data simultaneously, by locking out other threads when one thread is updating a value (in what’s called a critical sectionof your code).

A common concern when dealing with threads is the idea of code being thread-safe. Some software libraries are written with thread concurrency in mind and have all their critical sections properly protected with mutexes. Some code libraries aren’t thread-safe. For example, in Cocoa Touch, the Foundation framework is generally considered to be thread-safe. However, the UIKit framework (containing the classes specific to building GUI applications , such as UIApplication, UIView and all its subclasses, and so on) is, for the most part, not thread-safe. (Some UIKit functionality such as drawing is considered thread-safe however.) This means that in a running an iOS application, all method calls that deal with any UIKit objects should be executed from within the same thread, which is commonly known as the main thread. If you access UIKit objects from another thread, all bets are off. You are likely to encounter seemingly inexplicable bugs (or, even worse, you won’t experience any problems, but some of your users will be affected by them after you ship your app).

Tip

A lot has been written about thread safety. It’s well worth your time to dig in and try to digest as much of it as you can. One great place to start is Apple’s own documentation. Take a few minutes and read through this page (it will definitely help):

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html

Units of Work

The problem with the threading model described earlier is that, for the average programmer, writing error-free, multithreaded code is nearly impossible. This is not meant as a critique of our industry or of the average programmer’s abilities; it’s simply an observation. The complex interactions you must account for in your code when synchronizing data and actions across multiple threads are really just too much for most people to tackle. Imagine that 5% of all people have the capacity to write software at all. Only a small fraction of those 5% are really up to the task of writing heavy-duty multithreaded applications. Even people who have done it successfully will often advise others to not follow their example.

Fortunately, there are alternatives. It is possible to implement some concurrency without too much low-level detailed work. Just as we have the ability to display data on the screen without directly poking bits into video RAM and to read data from disk without interfacing directly with disk controllers, we can also leverage software abstractions that let us run our code on multiple threads without requiring us to do much directly with the threads.

The solutions Apple encourages us to use center around the idea of splitting up long-running tasks into units of work and putting those units into queues for execution. The system manages the queues for us, executing units of work on multiple threads. We don’t need to start or manage the background threads directly, and we are freed from much of the bookkeeping that’s usually involved in implementing multithreaded applications; the system takes care of that for us.

GCD: Low-Level Queuing

This idea of putting units of work into queues that can be executed in the background, with the system managing the threads for you, provides power and greatly simplifies many development situations where concurrency is needed. GCD made its debut on OS X (now macOS) several years ago, providing the infrastructure to do just that. A couple of years later, this technology came to the iOS platform as well. GCD puts some great concepts—units of work, painless background processing, and automatic thread management—into a C interface that can be used not only with Objective-C, but also with C , C++, and, of course, Swift. To top things off, Apple has made its implementation of GCD open source, so it can be ported to other Unix-like operating systems, as well.

One of the key concepts of GCD is the queue. The system provides a number of predefined queues, including a queue that’s guaranteed to always do its work on the main thread. It’s perfect for the non-thread-safe UIKit. You can also create your own queues—as many as you like. GCD queues are strictly first-in, first-out (FIFO) . Units of work added to a GCD queue will always be started in the order they were placed in the queue. That said, they may not always finish in the same order, since a GCD queue will automatically distribute its work among multiple threads, if possible.

GCD accesses a pool of threads that are reused throughout the lifetime of the application. It tries to maintain a number of threads appropriate for the machine’s architecture. It automatically takes advantage of a more powerful machine by utilizing more processor cores when it has work to do. Until a few years ago, iOS devices were all single-core, so this wasn’t much of an issue. But now that all iOS devices released in the past few years feature multicore processors, GCD has become truly useful.

GCD uses closures to encapsulate the code to be added to a queue. Closures are first-class language citizens in Swift—you can assign a closure to a variable, pass one to a method, or return one as the result of a method call. Closures are the equivalent of Objective-C’s blocks and similar features, sometimes referred to using the term lambdas, in other programming languages, such as Python. Much like a method or function, a closure can take one or more parameters and specify a return value, although closures used with GCD can neither accept arguments nor return a value. To declare a closure variable, you simply assign to it some code wrapped in curly braces, optionally with arguments:

// Declare a closure variable "loggerClosure" with no parameters
// and no return value.
let loggerClosure = {
    print("I'm just glad they didn't call it a lambda")
}

You can execute the closure in the same way as you call a function :

// Execute the closure, producing some output in the console.
loggerClosure()

Improving SlowWorker

To see how to use closures with GCD, let’s revisit SlowWorker’s doWork() method. It currently looks like this:

    @IBAction func doWork(_ sender: AnyObject) {
            let startTime = NSDate()
            self.resultsTextView.text = ""
            let fetchedData = self.fetchSomethingFromServer()
            let processedData = self.processData(fetchedData)
            let firstResult = self.calculateFirstResult(processedData)
            let secondResult = self.calculateSecondResult(processedData)
            let resultsSummary =
                "First: [(firstResult)] Second: [(secondResult)]"
            self.resultsTextView.text = resultsSummary
            let endTime = NSDate()
            print("Completed in (endTime.timeIntervalSince(startTime as Date)) seconds")
    }

We can make this method run entirely in the background by wrapping all the code in a closure and passing it to a GCD function called DispatchQueue. This function takes two parameters : a GCD queue and the closure to assign to the queue. Make the changes in Listing 15-2 to your copy of doWork().

Listing 15-2. Modifications to the doWork Method to Use GCD
    @IBAction func doWork(sender: AnyObject) {
        let startTime = NSDate()
        resultsTextView.text = ""
        let queue = DispatchQueue.global(qos: .default)
        queue.async {
            let fetchedData = self.fetchSomethingFromServer()
            let processedData = self.processData(fetchedData)
            let firstResult = self.calculateFirstResult(processedData)
            let secondResult = self.calculateSecondResult(processedData)
            let resultsSummary =
                "First: [(firstResult)] Second: [(secondResult)]"
            self.resultsTextView.text = resultsSummary
            let endTime = NSDate()
            print("Completed in (endTime.timeIntervalSince(startTime as Date)) seconds")
        }
    }

The first changed line grabs a preexisting global queue that’s always available, using the DispatchQueue.global() function. That function takes one arguments letting you specify a priority. If you specify a different priority in the argument you will actually get a different global queue, which the system will prioritize differently. For now, we’ll stick with the default global queue.

The queue is then passed to the queue.async() function, along with the closure. GCD takes the closure and puts it on the queue, from where it will be scheduled to run on a background thread and executed one step at a time, just as when it was running in the main thread.

Note that we defined a variable called startTime just before the closure is created, and then use its value at the end of the closure. Intuitively, this doesn’t seem to make sense because, by the time the closure is executed, the doWork() method has returned, so the NSDate instance that the startTime variable is pointing to should already be released! This is a crucial point to understand about closures: if a closure accesses any variables from “the outside” during its execution, then some special setup happens when the closure is created, allowing it to continue to access to them. All of this is done automatically by the Swift compiler and runtime—you don’t need to do anything special to make it happen.

Don’t Forget That Main Thread

Getting back to the project at hand, there’s one problem here: UIKit thread-safety. Remember that messaging any GUI object from a background thread, including our resultsTextView, is a no-no. In fact, if you run the example now, you’ll see an exception appear in the Xcode console after about ten seconds, when the closure tries to update the text view. Fortunately, GCD provides a way to deal with this, too. Inside the closure, we can call another dispatching function, passing work back to the main thread. Make one additional change to your version of doWork(), as shown in Listing 15-3.

Listing 15-3. The modified doWork Method
    @IBAction func doWork(sender: AnyObject) {
        let startTime = NSDate()
        resultsTextView.text = ""
        let queue = DispatchQueue.global(attributes: DispatchQueue.GlobalAttributes.qosDefault)
        queue.async {
            let fetchedData = self.fetchSomethingFromServer()
            let processedData = self.processData(fetchedData)
            let firstResult = self.calculateFirstResult(processedData)
            let secondResult = self.calculateSecondResult(processedData)
            let resultsSummary =
                "First: [(firstResult)] Second: [(secondResult)]"
            DispatchQueue.main.async {
                self.resultsTextView.text = resultsSummary
            }
            let endTime = NSDate()
            print("Completed in (endTime.timeIntervalSince(startTime as Date)) seconds")


        }
    }

Giving Some Feedback

If you build and run your app at this point, you’ll see that it now seems to work a bit more smoothly, at least in some sense. The button no longer gets stuck in a highlighted position after you touch it, which perhaps leads you to tap again, and again, and so on. If you look in the Xcode console log, you’ll see the result of each of those taps, but only the results of the last tap will be shown in the text view. What we really want to do is enhance the GUI so that, after the user presses the button, the display is immediately updated in a way that indicates that an action is underway. We also want the button to be disabled while the work is in progress so that the user can’t keep clicking it to spawn more and more work into background threads. We’ll do this by adding a UIActivityIndicatorViewto our display. This class provides the sort of spinner seen in many applications and web sites. Start by adding an outlet for it at the top of ViewController.swift :

    @IBOutlet var spinner : UIActivityIndicatorView!              

Next, open Main.Storyboard, locate an Activity Indicator View in the library, and drag it into our view, next to the button. You’ll need to add layout constraints to fix the activity indicator’s position relative to the button. One way to do this is to Control-drag from the button to the activity indicator and select Horizontal Spacing from the pop-up menu to fix the horizontal separation between them, and then Control-drag again and select Center Vertically to make sure that their centers remain vertically aligned.

With the activity indicator spinner selected, use the Attributes Inspector to check the Hides When Stopped check box so that our spinner will appear only when we tell it to start spinning (no one wants an unspinning spinner in their GUI). Next, Control-drag from the View Controller icon to the spinner and connect the spinner outlet. Save your changes.

Now open ViewController.swift. Here, we’ll first work on the doWork() method a bit, adding a few lines to manage the appearance of the button and the spinner when the user taps the button and when the work is done. We’ll first set the button’s enabled property to false, which prevents it from registering any taps and also shows that the button is disabled by making its text gray and somewhat transparent. Next, we get the spinner moving by calling its startAnimating () method . At the end of the closure, we re-enable the button and stop the spinner, which causes it to disappear again, as shown in Listing 15-4.

Listing 15-4. Adding the Spinner Functions to our doWork Method
    @IBAction func doWork(sender: AnyObject) {
        let startTime = NSDate()
        resultsTextView.text = ""
        startButton.isEnabled = false
        spinner.startAnimating()
        let queue = DispatchQueue.global(qos: .default)
        queue.async {
            let fetchedData = self.fetchSomethingFromServer()
            let processedData = self.processData(fetchedData)
            let firstResult = self.calculateFirstResult(processedData)
            let secondResult = self.calculateSecondResult(processedData)
            let resultsSummary =
                "First: [(firstResult)] Second: [(secondResult)]"
            DispatchQueue.main.async {
                self.resultsTextView.text = resultsSummary
                self.startButton.isEnabled = true
                self.spinner.stopAnimating()
            }
            let endTime = NSDate()
            print("Completed in (endTime.timeIntervalSince(startTime as Date)) seconds")


        }
    }

Build and run the app, and press the button. Even though the work being done takes a few seconds, the user isn’t just left hanging. The button is disabled and looks the part as well. Also, the animated spinner lets the user know that the app hasn’t actually hung up and can be expected to return to normal at some point.

Concurrent Closures

The sharp-eyed among you will notice that, after going through these motions, we still haven’t really changed the basic sequential layout of our algorithm (if you can even call this simple list of steps an algorithm). All that we’re doing is moving a chunk of this method to a background thread and then finishing up in the main thread. The Xcode console output proves it: this work takes ten seconds to run, just as it did at the outset. The issue is that the calculateFirstResult() and calculateSecondResult() methods don’t depend on each and therefore don’t need to be called in sequence. Doing them concurrently gives us a substantial speedup.

Fortunately, GCD has a way to accomplish this by using what’s called a dispatch group. All closures that are dispatched asynchronously within the context of a group, via the dispatch_group_async() function, are set loose to execute as fast as they can, including being distributed to multiple threads for concurrent execution, if possible. We can also use dispatch_group_notify() to specify an additional closure that will be executed when all the closures in the group have been run to completion.

Make these final changes to the doWork method, as shown in Listing 15-5.

Listing 15-5. The Final Version of Our doWork Method
    @IBAction func doWork(_ sender: AnyObject) {
        let startTime = Date()
        self.resultsTextView.text = ""
        startButton.isEnabled = false
        spinner.startAnimating()
        let queue = DispatchQueue.global(qos: .default)
        queue.async {
            let fetchedData = self.fetchSomethingFromServer()
            let processedData = self.processData(fetchedData)
            var firstResult: String!
            var secondResult: String!
            let group = DispatchGroup()


            queue.async(group: group) {
                firstResult = self.calculateFirstResult(processedData)
            }
            queue.async(group: group) {
                secondResult = self.calculateSecondResult(processedData)
            }


            group.notify(queue: queue) {
                let resultsSummary = "First: [(firstResult!)] Second: [(secondResult!)]"
                DispatchQueue.main.async {
                    self.resultsTextView.text = resultsSummary
                    self.startButton.isEnabled = true
                    self.spinner.stopAnimating()
                }
                let endTime = Date()
                print("Completed in (endTime.timeIntervalSince(startTime)) seconds")
            }
        }
    }

One complication here is that each of the calculate methods returns a value that we want to grab, so we need to make sure that the variables firstResult and secondResult can be assigned from the closures. To do this, we declare them using var instead of let. However, Swift requires a variable that’s referenced from a closure to be initialized, so the following declarations don’t work:

var firstResult: String
var secondResult: String

You can, of course, work around this problem by initializing both variables with an arbitrary value, but it’s easier to make them implicitly unwrapped optionals by adding ! to the declaration:

var firstResult: String!              
var secondResult: String!

Now, Swift doesn’t require an initialization, but we need to be sure that both variables will have a value when they are eventually read. In this case, we can be sure of that, because the variables are read in the completion closure for the async group, by which time they are certain to have been assigned a value. With this in place, build and run the app again. You’ll see that your efforts have paid off. What was once a ten-second operation now takes just seven seconds, thanks to the fact that we’re running both of the calculations simultaneously.

Obviously, our contrived example gets the maximum effect because these two “calculations” don’t actually do anything but cause the thread they’re running on to sleep. In a real application, the speedup would depend on what sort of work is being done and what resources are available. The performance of CPU-intensive calculations is helped by this technique only if multiple CPU cores are available. It will get better almost for free as more cores are added to future iOS devices. Other uses, such as fetching data from multiple network connections at once, would see a speed increase even with just one CPU.

As you can see, GCD is not a panacea. Using GCD won’t automatically speed up every application. But by carefully applying these techniques at those points in your app where speed is essential, or where you find that your application feels like it’s lagging in its responses to the user, you can easily provide a better user experience, even in situations where you can’t improve the real performance.

Background Processing

Another important technology for handling concurrency is background processing. This allows your apps to run in the background—in some circumstances, even after the user has pressed the home button.

This functionality should not be confused with the true multitasking that modern desktop operating systems now feature, where all the programs you launch remain resident in the system RAM until you explicitly quit them (or until the operating system needs to free up some space and starts swapping them to disk). iOS devices still have too little RAM to be able to pull that off very well. Instead, this background processing is meant to allow applications that require specific kinds of system functionality to continue to run in a constrained manner when they are in the background. For instance, if you have an app that plays an audio stream from an Internet radio station, iOS will let that app continue to run, even if the user switches to another app. Beyond that, it will even provide standard pause and volume controls in the iOS control center (the translucent control panel that appears when you swipe up from the bottom of the screen) while your app is playing audio.

Assume you’re creating an app that does one of the following things: plays audio even when the user is running another app, requests continuous location updates, responds to a special type of push request telling it to load new data from a server, or implements Voice over IP (VoIP) to let users send and receive phone calls on the Internet. In each of these cases, you can declare this situation in your app’s Info.plist file, and the system will treat your app in a special way. This usage, while interesting, is probably not something that most readers of this book will be tackling right away, so we’re not going to delve into it here.

Besides running apps in the background, iOS also includes the ability to put an app into a suspended state after the user presses the home button. This state of suspended execution is conceptually similar to putting your Mac into sleep mode. The entire working memory of the application is held in RAM; it just isn’t executed while suspended. As a result, switching back to such an application is lightning-fast. This isn’t limited to special applications. In fact, it is the default behavior of any app you build with Xcode (though this can be disabled by another setting in the Info.plist file). To see this in action, open your device’s Mail application and drill down into a message. Next, press the home button, open the Notes application, and select a note. Now double-tap the home button and switch back to Mail. You’ll see that there’s no perceptible lag; it just slides into place as if it had been ru nning all along.

For most applications, this sort of automatic suspension and resumption is all you’re likely to need. However, in some situations, your app may need to know when it’s about to be suspended and when it has just been awakened. The system provides ways of notifying an app about changes to its execution state via the UIApplication class, which has a number of delegate methods and notifications for just this purpose. I’ll show you how to use them later in this chapter.

When your application is about to be suspended, one thing it can do, regardless of whether it’s one of the special backgroundable application types, is request a bit of additional time to run in the background. The idea is to make sure your app has enough time to close any open files, network resources, and so on. We’ll see an example of this shortly.

Application Life Cycle

Before we get into the specifics of how to deal with changes to your app’s execution state, let’s talk a bit about the various states in its life cycle:

  • Not Running: This is the state that all apps are in on a freshly rebooted device. An application that has been launched at any point after the device is turned on will return to this state only under specific conditions:

    • If its Info.plist includes the UIApplicationExitsOnSuspend key (with its value set to YES)

    • If it was previously Suspended and the system needs to clear out some memory

    • If it crashes while running

  • Active: This is the normal running state of an application when it’s displayed on the screen. It can receive user input and update the display.

  • Background: In this state, an app is given some time to execute some code, but it can’t directly access the screen or get any user input. All apps enter this state briefly when the user presses the home button; most of them quickly move on to the Suspended state. Apps that want to do any sort of background processing stay in this state until they’re made Active again.

  • Suspended: A Suspended app is frozen. This is what happens to normal apps after their brief stint in the Background state. All the memory the app was using while it was active is held just as it was. If the user brings the app back to the Active state, it will pick up right where it left off. On the other hand, if the system needs more memory for whichever app is currently Active, any Suspended apps may be terminated (and placed back into the Not Running state) and their memory freed for other use.

  • Inactive: An app enters the Inactive state only as a temporary rest stop between two other states. The only way an app can stay Inactive for any length of time is if the user is dealing with a system prompt (such as those shown for an incoming call or SMS message) or if the user has locked the screen. This state is basically a sort of limbo.

State-Change Notifications

To manage changes between these states, UIApplicationdefines a number of methods that its delegate can implement. In addition to the delegate methods, UIApplication also defines a matching set of notification names (see Table 15-1). This allows other objects besides the app delegate to register for notifications when the application’s state changes.

Table 15-1. Delegate Methods for Tracking Your Application’s Execution State and Their Corresponding Notification Names

Delegate Method

Notification Name

application(_:didFinishLaunchingWithOptions:)

UIApplicationDidFinishLaunching

applicationWillResignActive()

UIApplicationWillResignActive

applicationDidBecomeActive()

UIApplicationDidBecomeActive

applicationDidEnterBackground()

UIApplicationDidEnterBackground

applicationWillEnterForeground()

UIApplicationWillEnterForeground

applicationWillTerminate()

UIApplicationWillTerminate

Note that each of these methods is directly related to one of the running states : Active, Inactive, and Background. Each delegate method is called (and each notification posted) in only one of those states. The most important state transitions are between Active and other states. Some transitions, like from Background to Suspended, occur without any notice whatsoever. Let’s go through these methods and discuss how they’re meant to be used.

The first of these, application(_:didFinishLaunchingWithOptions:), we’ve already seen many times in this book. It’s the primary way of doing application-level coding directly after the app has launched. There is a similar method called application(_:willFinishLaunchingWithOptions:) that’s called first and which is intended for applications that use the view controller-based state save and restore feature (which is beyond the scope of this book). That method is not listed here because it’s not associated with a state change.

The next two methods , applicationWillResignActive() and applicationDidBecomeActive(), are both used in a number of circumstances. If the user presses the home button, applicationWillResignActive() gets called. If the user later brings the app back to the foreground, applicationDidBecomeActive() is called. The same sequence of events occurs if the user receives a phone call. applicationDidBecomeActive() is also called when the application launches for the first time. In general, this pair of methods brackets the movement of an application from the Active state to the Inactive state. They are good places to enable and disable any animations, in-app audio, or other items that deal with the app’s presentation to the user. Because of the multiple situations where applicationDidBecomeActive() is used, you may want to put some of your app initialization code there instead of in application(_:didFinishLaunchingWithOptions:). Note that you should not assume in applicationWillResignActive() that the application is about to be sent to the background; it may just be a temporary change that ends up with a move back to the Active state.

After those methods we see applicationDidEnterBackground() and applicationWillEnterForeground(), which have a slightly different usage area : dealing with an app that is definitely being sent to the background. applicationDidEnterBackground() is where your app should free all resources that can be re-created later, save all user data, close network connections, and so forth. This is also the spot where you can request more time to run in the background if you need it, as we’ll see shortly. If you spend too much time doing things in applicationDidEnterBackground()—more than about five seconds—the system will decide that your app is misbehaving and terminate it. You should implement applicationWillEnterForeground() to re-create whatever was torn down in applicationDidEnterBackground(), such as reloading user data, reestablishing network connections, and so on. Note that when applicationDidEnterBackground() is called, you can safely assume that applicationWillResignActive() has also been recently called. Likewise, when applicationWillEnterForeground() gets called, you can assume that applicationDidBecomeActive() will soon be called, as well.

Finally, applicationWillTerminate(), which you’ll probably rarely use, if ever, is called only if your application is already in the background and the system decides to skip suspension for some reason and simply terminate the app.

Now that you have a basic theoretical understanding of the states an application transitions between, let’s put this knowledge to the test with a simple app that does nothing more than write a message to Xcode’s console log each time one of these methods is called. We’ll then manipulate the running app in a variety of ways, just as a user might, and see which transitions occur. To get the most out of this example, you’ll need an iOS device. If you don’t have one, you can use the simulator and skip over the parts that require a device.

Creating State Lab

In Xcode, create a new project based on the Single View Application template and name it State Lab. Initially at least, this app won’t display anything but the default white screen it’s born with. Later, we’ll make it do something more interesting, but for now, all the output it’s going to generate will end up in the Xcode console. The AppDelegate.swift file already contains all the methods we’re interested in. We just need to add some logging, as shown in bold. Note that we’ve also removed the comments from these methods, as shown in Listing 15-6, just for the sake of brevity.

Listing 15-6. The AppDelegate.swift Logging Methods
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        print(#function)
        return true
    }


    func applicationWillResignActive(_ application: UIApplication) {
        print(#function)
    }


    func applicationDidEnterBackground(_ application: UIApplication) {
        print(#function)
    }


    func applicationWillEnterForeground(_ application: UIApplication) {
        print(#function)
    }


    func applicationDidBecomeActive(_ application: UIApplication) {
        print(#function)
    }


    func applicationWillTerminate(_ application: UIApplication) {
        print(#function)
    }

You may be wondering about the value that’s being passed to the print() function in each of these methods: the literal expression #function evaluates to the name of the method in which it appears. Here, we are using it to get the current method name without needing to retype it or copy and paste it into each of the lifecycle methods.

Exploring Execution States

Now build and run the app and take a look at the console (View ➤ Debug Area ➤ Activate Console), where you should see something like this:

application(_:didFinishLaunchingWithOptions:)
applicationDidBecomeActive

among a number of other messages. You can use the search field below the console output to filter what you observe.

Here, you can see that the application has successfully launched and been moved into the Active state. Now press the home button (if you’re using the simulator, you’ll have to do this by selecting Hardware ➤ Home from the simulator’s menu or ⇧⌘H on the keyboard). You should see the following in the console:

applicationWillResignActive
applicationDidEnterBackground

These two lines show the app actually transitioning between two states: it first becomes Inactive, and then goes to Background. What you can’t see here is that the app also switches to a third state: Suspended. Remember that you do not get any notification that this has happened; it’s completely outside your control. Note that the app is still live in some sense, and Xcode is still connected to it, even though it’s not actually getting any CPU time. Verify this by tapping the app’s icon to relaunch it, which should produce this output:

applicationWillEnterForeground
applicationDidBecomeActive

There you are, back in business. The app was previously Suspended, is woken up to Inactive, and then ends up Active again. So, what happens when the app is really terminated? Tap the home button again and you’ll see this:

applicationWillResignActive
applicationDidEnterBackground

Now double-tap the home button (or on the simulator, press ⇧⌘HH—you need to press the H key twice). The sideways-scrolling screen of apps should appear. Press and swipe upward on the State Lab screenshot until it flies offscreen, killing the application. You should see something like:

2016-07-21 10:15:40.201746 temp[2825:864732] [Common] <FBSUIApplicationWorkspaceClient [0x6080000f8700]>: Received exit event
applicationDidEnterBackground
applicationWillTerminate
Tip

Do not rely on the applicationWillTerminate() method being called to save the state of your application—do this in applicationDidEnterBackground() instead.

There’s one more interesting interaction to examine here. It’s what happens when the system shows an alert dialog, temporarily taking over the input stream from the app and putting it into an Inactive state. This state can be readily triggered only when running on a real device instead of the simulator, using the built-in Messages app. Messages, like many other apps, can receive messages from the outside and display them in several ways.

To see how these are set up, run the Settings app on your device, choose Notifications from the list, and then select the Messages app from the list of apps. The hot “new” way to show messages, which debuted way back in iOS 5, is called Banners. This works by showing a small banner overlaid at the top of the screen, which doesn’t need to interrupt whatever app is currently running. What we want to show is the bad old Alerts method, which makes a modal panel appear in front of the current app, requiring a user action. Under the heading ALERT STYLE WHEN UNLOCKED, select Alerts so that the Messages app turns back into the kind of pushy jerk that users of iOS 4 and earlier always had to deal with.

Now back to your computer. In Xcode, use the pop-up at the upper left to switch from the simulator to your device, and then hit the Run button to build and run the app on your device. Now all you need to do is send a message to your device from the outside. If your device is an iPhone, you can send it an SMS message from another phone. If it’s an iPod touch or an iPad, you’re limited to Apple’s own iMessage communication, which works on all iOS devices, as well as OS X in the Messages app. Figure out what works for your setup, and send your device a message via SMS or iMessage. When your device displays the system alert showing the incoming message, this will appear in the Xcode console:

applicationWillResignActive

Note that our app didn’t get sent to the background. It’s in the Inactive state and can still be seen behind the system alert. If this app were a game or had any video, audio, or animations running, this is where we would probably want to pause them.

Press the Close button on the alert, and you’ll get this:

applicationDidBecomeActive

Now let’s see what happens if you decide to reply to the message instead. Send another message to your device, generating this:

applicationWillResignActive

This time, hit Reply, which switches you over to the Messages app. You should see the following flurry of activity:

applicationDidBecomeActive
applicationWillResignActive
applicationDidEnterBackground

Our app quickly becomes Active , becomes Inactive again, and finally goes to Background (and then, silently, Suspended).

Using Execution State Changes

So, what should we make of all this? Based on what was just demonstrated, it seems like there’s a clear strategy to follow when dealing with these state changes:

Active ➤ Inactive

Use applicationWillResignActive()/ UIApplicationWillResignActivenotification to “pause” your app’s display. If your app is a game, you probably already have the ability to pause the gameplay in some way. For other kinds of apps, make sure no time-critical demands for user input are in the works because your app won’t be getting any user input for a while.

Inactive ➤ Background

Use applicationDidEnterBackground()/ UIApplicationDidEnterBackgroundnotification to release any resources that don’t need to be kept around when the app is backgrounded (such as cached images or other easily reloadable data) or that might not survive backgrounding anyway (such as active network connections). Getting rid of excess memory usage here will make your app’s eventual Suspended snapshot smaller, thereby decreasing the risk that your app will be purged from RAM entirely. You should also use this opportunity to save any application data that will help your users pick up where they left off the next time your app is relaunched. If your app comes back to the Active state, normally this won’t matter; however, in case it’s purged and must be relaunched, your users will appreciate starting off in the same place.

Background ➤ Inactive

Use applicationWillEnterForeground()/UIApplicationWillEnterForegroundnotification to undo anything you did when switching from Inactive to Background. For example, here you can reestablish persistent network connections.

Inactive ➤ Active

Use applicationDidBecomeActive()/UIApplicationDidBecomeActivenotification to undo anything you did when switching from Active to Inactive. Note that, if your app is a game, this probably does not mean dropping out of pause straight to the game; you should let your users do that on their own. Also keep in mind that this method and notification are used when an app is freshly launched, so anything you do here must work in that context, as well.

There is one special consideration for the Inactive ➤ Background transition. Not only does it have the longest description in the previous list, but it’s also probably the most code- and time-intensive transition in applications because of the amount of bookkeeping you may want your app to do. When this transition is underway, the system won’t give you the benefit of an unlimited amount of time to save your changes here. It gives you a few seconds. If your app takes longer than that to return from the delegate method (and handle any notifications you’ve registered for), then your app will be summarily purged from memory and pushed into the Not Running state. If this seems unfair, don’t worry because there is a reprieve available. While handling that delegate method or notification, you can ask the system to perform some additional work for you in a background queue, which buys you some extra time.

Handling the Inactive State

The simplest state change your app is likely to encounter is from Active to Inactive, and then back to Active. You may recall that this is what happens if your iPhone receives an SMS message while your app is running and displays it for the user. In this section, we’re going to make State Lab do something visually interesting so that you can see what happens if you ignore that state change. Next, I’ll show you how to fix it.

We’ll also add a UILabelto our display and make it move using Core Animation, which is a really nice way of animating objects in iOS.

Start by adding a UILabel in ViewController.swift:

class ViewController: UIViewController {
    private var label:UILabel!

Now let’s set up the label when the view loads. Modify the viewDidLoad() method as shown in Listing 15-7.

Listing 15-7. Our Modified viewDidLoad Method
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        let bounds = view.bounds


        let labelFrame = CGRect(origin: CGPoint(x: bounds.origin.x, y: bounds.midY - 50) , size: CGSize(width:  bounds.size.width, height: 100))
        label = UILabel(frame: labelFrame)
        label.font = UIFont(name: "Helvetica", size:70)
        label.text = "Bazinga!"
        label.textAlignment = NSTextAlignment.center
        label.backgroundColor = UIColor.clear()
        view.addSubview(label)
    }

This vertically centers the label in its parent view and makes it stretch across the full width of its parent. Next, let’s set up some animation. We’ll define two methods: one to rotate the label to an upside-down position :

    func rotateLabelDown() {
        UIView.animate(withDuration: 0.5, animations: {
                self.label.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI))
            },
            completion: {(Bool) -> Void in
                self.rotateLabelUp()
            }
        )
    }

This one rotates it back to normal:

    func rotateLabelUp() {
        UIView.animate(withDuration: 0.5, animations: {
                self.label.transform = CGAffineTransform(rotationAngle: 0)
            },
            completion: {(Bool) -> Void in
                    self.rotateLabelDown()
            }
        )
    }

This deserves a bit of explanation. UIView defines a class method called animate(withDuration: completion:), which sets up an animation. Attributes that can be animated, which we set within the animations closure, don’t have an immediate effect on the receiver. Instead, Core Animation will smoothly transition that attribute from its current value to the new value we specify. This is what’s called an implicit animation, which is one of the main features of Core Animation. The completion closure lets us specify what will happen after the animation is complete. Note carefully the syntax of this closure:

            completion: {(Bool) -> Void in
                if self.animate {
                    self.rotateLabelDown()
                }
            }

The code in bold is the signature of the closure—it says that the closure is called with a single Boolean argument and returns nothing. The argument has a value of true if the animation completed normally, false if it was cancelled. In this example, we don’t make any use of this argument.

So, each of these methods sets the label’s transform property to a particular rotation angle, specified in radians, and uses the completion closure to call the other method, so the text will continue to animate back and forth forever.

Finally, we need to set up a way to kick-start the animation. For now, we’ll do this by adding this line at the end of viewDidLoad():

    rotateLabelDown();

Build and run the app. You should see the Bazinga! label rotate back and forth, as shown in Figure 15-4.

A329781_3_En_15_Fig4_HTML.jpg
Figure 15-4. The State Lab application rotating the label

To test the Active ➤ Inactive transition , you really need to once again run this on an actual iPhone and send an SMS message to it from elsewhere. Build and run the app on an iPhone, and see that the animation is running along. Now send an SMS message to the device. When the system alert comes up to show the message, you’ll see that the animation keeps on running. That may be slightly comical, but it’s probably irritating for a user. We will use application state transition notifications to stop our animation when this occurs.

Our controller class will need to have some internal state to keep track of whether it should be animating at any given time. For this purpose, let’s add a property to the ViewController class :

class ViewController: UIViewController {
    private var label:UILabel!
    private var animate = false

As you’ve seen, changes in the application state are notified to the application delegate, but since our class isn’t the application delegate, we can’t just implement the delegate methods and expect them to work. Instead, we sign up to receive notifications from the application when its execution state changes. Do this by adding the following code to the end of the viewDidLoad method in ViewController.swift :

        let center = NotificationCenter.default
        center.addObserver(self, selector: #selector(ViewController.applicationWillResignActive),
               name: Notification.Name.UIApplicationWillResignActive, object: nil)
         center.addObserver(self, selector: #selector(ViewController.applicationDidBecomeActive),
               name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)

This sets up the notifications so that each will call a method in our class at the appropriate time. Add the following methods to the ViewController class :

    func applicationWillResignActive() {
        print("VC: (#function)")
        animate = false
    }


    func applicationDidBecomeActive() {
        print("VC: (#function)")
        animate = true
        rotateLabelDown()
    }

These methods include the same method logging as before, just so you can see where the methods occur in the Xcode console . We added the preface "VC: " to distinguish this call from the similar calls in the delegate (VC is for view controller). The first of these methods just turns off the animate flag. The second turns the flag back on, and then actually starts up the animations again. For that first method to have any effect, we need to add some code to check the animate flag and keep on animating only if it’s enabled:

    func rotateLabelUp() {
        UIView.animate(withDuration: 0.5, animations: {
                self.label.transform = CGAffineTransform(rotationAngle: 0)
            },
            completion: {(Bool) -> Void in
                if self.animate {
                    self.rotateLabelDown()
                }
            }
        )
    }

We added this to the completion block of rotateLabelUp(), (and only there) so that our animation will stop only when the text is right-side up. Finally, since we are now starting the animation when the application becomes active, and this happens right after it is launched, we no longer need the call rotateLabelDown() in viewDidLoad(), so delete it:

override func viewDidLoad() {

    rotateLabelDown();

    let center = NSNotificationCenter.default

Now build and run the app again, and you should see that it’s animating as before. Once again, send an SMS message to your iPhone. This time, when the system alert appears, you’ll see that the animation in the background stops as soon as the text is right-side up. Tap the Close button, and the animation starts back up.

Now you’ve seen what to do for the simple case of switching from Active to Inactive and back. The bigger task, and perhaps the more important one, is dealing with a switch to the background and then back to foreground.

Handling the Background State

As mentioned earlier, switching to the Background state is pretty important to ensure the best possible user experience. This is the spot where you’ll want to discard any resources that can easily be reacquired (or will be lost anyway when your app goes silent) and save information about your app’s current state, all without occupying the main thread for more than five seconds.

To demonstrate some of these behaviors, we’re going to extend State Lab in a few ways. First, we’re going to add an image to the display so that I can later show you how to get rid of the in-memory image. Then I’m going to show you how to save some information about the app’s state, so we can easily restore it later. Finally, I’ll show you how to make sure these activities aren’t taking up too much main thread time by putting all this work into a background queue.

Removing Resources When Entering the Background

Start by adding smiley.png from the 15 - Image folder in the book’s source archive to your project’s State Lab folder. Be sure to enable the check box that tells Xcode to copy the file to your project directory. Don’t add it to the Assets.xcassets asset catalog because that would provide automatic caching, which would interfere with the specific resource management we’re going to implement.

Now let’s add properties for both the image and an image view to ViewController.swift :

class ViewController: UIViewController {
    private var label:UILabel!
    private var smiley:UIImage!
    private var smileyView:UIImageView!
    private var animate = false

Next, set up the image view and put it on the screen by modifying the viewDidLoad() method , as shown in Listing 15-7.

Listing 15-7. Modified viewDidLoad Methods
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.


        let bounds = view.bounds
        let labelFrame = CGRect(origin: CGPoint(x: bounds.origin.x, y: bounds.midY - 50) , size: CGSize(width:  bounds.size.width, height: 100))
        label = UILabel(frame:labelFrame)
        label.font = UIFont(name:"Helvetica", size:70)
        label.text = "Bazinga!"
        label.textAlignment = NSTextAlignment.center
        label.backgroundColor = UIColor.clear()


        // smiley.png is 84 x 84
        let smileyFrame = CGRect(x: bounds.midX - 42,
            y: bounds.midY/2 - 42, width: 84, height: 84)


        smileyView = UIImageView(frame:smileyFrame)
        smileyView.contentMode = UIViewContentMode.center
        let smileyPath =
            Bundle.main.pathForResource("smiley", ofType: "png")!
        smiley = UIImage(contentsOfFile: smileyPath)
        smileyView.image = smiley
        view.addSubview(smileyView)


        view.addSubview(label)

        let center = NotificationCenter.default
        center.addObserver(self, selector: #selector(ViewController.applicationWillResignActive),
                name: NSNotification.Name.UIApplicationWillResignActive, object: nil)
        center.addObserver(self, selector: #selector(ViewController.applicationDidBecomeActive),
                name: NSNotification.Name.UIApplicationDidBecomeActive, object: nil)
    }

Build and run the app. You’ll see the incredibly happy-looking smiley face toward the top of your screen, as shown in Figure 15-5.

A329781_3_En_15_Fig5_HTML.jpg
Figure 15-5. The State Lab application rotating the label with the addition of a smiley icon

Next, press the home button to switch your app to the background, and then tap its icon to launch it again. You’ll see that when the app resumes, the label starts rotating again, as expected. All seems well, but in fact, we’re not yet optimizing system resources as well as we could. Remember that the fewer resources we use while our app is suspended, the lower the risk that iOS will terminate our app entirely. By clearing any easily re-created resources from memory when we can, we increase the chance that our app will stick around and therefore relaunch super-quickly.

Let’s see what we can do about that smiley face. We would really like to free up that image when going to the Background state and re-create it when coming back from the Background state. To do that, we’ll need to add two more notification registrations inside viewDidLoad():

        center.addObserver(self, selector: #selector(ViewController.applicationDidEnterBackground),
                    name: NSNotification.Name.UIApplicationDidEnterBackground, object: nil)
        center.addObserver(self, selector: #selector(ViewController.applicationWillEnterForeground),
                    name: NSNotification.Name.UIApplicationWillEnterForeground, object: nil)

And we want to implement the two new methods :

func applicationDidEnterBackground() {                  
    print("VC: (#function)")
    self.smiley = nil;
    self.smileyView.image = nil;
}
func applicationWillEnterForeground() {
    print("VC: (__FUNCTION__)")
    let smileyPath =
        Bundle.main.path(forResource:"smiley", ofType:"png")!
    smiley = UIImage(contentsOfFile: smileyPath)
    smileyView.image = smiley
}

Build and run the app, and repeat the same steps of backgrounding your app and switching back to it. You should see that, from the user’s standpoint, the behavior appears to be about the same. If you want to verify for yourself that this is really happening, comment out the contents of the applicationWillEnterForeground() method , and then build and run the app again. You’ll see that the image really does disappear.

Saving State When Entering the Background

Now that you’ve seen an example of how to free up some resources when entering the Background state, it’s time to think about saving state. Remember that the idea is to save information relevant to what the user is doing, so that if your application is later dumped from memory, users can still pick up right where they left off the next time they return.

The kind of state we’re talking about here is really application-specific, not view-specific. Do not confuse this with saving and restoring the locations of views or which screen of your application the user was looking at when it was last active—for that, iOS provides the state saving and restoration mechanism, which you can read about in the iOS App Programming Guide on Apple’s web site ( https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/StrategiesforImplementingYourApp/StrategiesforImplementingYourApp.html ). Here, we’re thinking about things like user preferences in applications for which you do not want to implement a separate settings bundle. Using the same UserDefaults API that we introduced you to in Chapter 12, you can quickly and easily save preferences from within the application and read them back later. Of course, if your application is not visually complex or you don’t want to use the state saving and restoration mechanism, you can save information that will allow you to restore its visual state in the user preferences, too.

The State Lab example is too simple to have real user preferences, so let’s take a shortcut and add some application-specific state to its one and only view controller. Add a property called index in ViewController.swift, along with a segmented control :

class ViewController: UIViewController {
    private var label:UILabel!
    private var smiley:UIImage!
    private var smileyView:UIImageView!
    private var segmentedControl:UISegmentedControl!
    private var index = 0
    private var animate = false

We’re going to allow the user to set the value of this property using a segmented control and we’re going to save it in the user defaults. We’re then going to terminate and relaunch the application, to demonstrate that we can recover the value of the property.

Next, move to the middle of the viewDidLoad() method, where you’ll create the segmented control, and add it to the view:

        smileyView.image = smiley
        segmentedControl =
            UISegmentedControl(items: ["One","Two", "Three", "Four"])
        segmentedControl.frame = CGRect(x: bounds.origin.x + 20, y: 50,
            width: bounds.size.width - 40, height: 30)
        segmentedControl.addTarget(self, action: #selector(ViewController.selectionChanged(_:)),
            for: UIControlEvents.valueChanged)


        view.addSubview(segmentedControl)
        view.addSubview(smileyView)

We also used the addTarget(_:action:forControlEvents) method to connect the segmented control to the selectionChanged() method, which we need to have called when the selected segment changes. Add the implementation of this method anywhere in the implementation of the ViewController class:

    func selectionChanged(_ sender:UISegmentedControl) {
            index = segmentedControl.selectedSegmentIndex;
    }

Now whenever the user changes the selected segment, the value of the index property will be updated.

Build and run the app. You should see the segmented control and be able to click its segments to select them one at a time. As you do so, the value of the index property will change, although you can’t actually see this happening. Background your app again by clicking the home button, bring up the task manager (by double-clicking the home button) and kill your app, and then relaunch it. When the application restarts, the index property will have a value of zero again and there will be no selected segment. That’s what we need to fix next.

Saving the value of the index property is simple enough; we just need one line of code to the end of the applicationDidEnterBackground() method in ViewController.swift:

func applicationDidEnterBackground() {                
    print("VC: (#function)")
    self.smiley = nil;
    self.smileyView.image = nil;
    UserDefaults.standard.set(self.index,
         forKey:"index")
}

But where should we restore the property value and use it to configure the segmented control? The inverse of this method, applicationWillEnterForeground(), isn’t what we want. When that method is called, the app has already been running, and the setting is still intact. Instead, we need to access this when things are being set up after a new launch, which brings us back to the viewDidLoad() method. Add the bold lines shown here to that method:

        view.addSubview(label)

        index = UserDefaults.standard.integer(forKey: "index")
        segmentedControl.selectedSegmentIndex = index;

When the application is being launched for the first time, there will not be a value saved in the user defaults. In this case, the integerForKey() method returns the value zero, which happens to be the correct initial value for the index property. If you wanted to use a different initial value, you could do so by registering it as the default value for the index key, as described in “Registering Default Values” in Chapter 12.

Now build and run the app. You’ll notice a difference immediately—the first segment in the segmented control is preselected, because its selected segment index was set in the viewDidLoad() method. Now touch a segment, and then do the full background-kill-restart dance. There it is—the index value has been restored and, as a result, the correct segment in the segmented control is now selected.

Obviously, what we’ve shown here is pretty minimal, but the concept can be extended to all kinds of application states. It’s up to you to decide how far you want to take it in order to maintain the illusion for the users that your app was always there, just waiting for them to come back.

Requesting More Backgrounding Time

Earlier, I mentioned the possibility of your app being dumped from memory if moving to the Background state takes too much time. For example, your app may be in the middle of doing a file transfer that it would really be a shame not to finish; however, trying to hijack the applicationDidEnterBackground()method to make it complete the work there, before the application is really backgrounded, isn’t really an option. Instead, you should use applicationDidEnterBackground()as a point for telling the system that you have some extra work you would like to do, and then start up a block to actually do it. Assuming that the system has enough available RAM to keep your app in memory while the user does something else, the system will oblige you and keep your app running for a while.

Let’s demonstrate this, not with an actual file transfer, but with a simple sleep call. Once again, we’ll be using our new acquaintances GCD to make the contents of our applicationDidEnterBackground() method run in a separate queue.

In ViewController.swift , modify the applicationDidEnterBackground() method as shown in Listing 15-8.

Listing 15-8. The Updated applicationDidEnterBackground Method
    func applicationDidEnterBackground() {
        print("VC: (#function)")
        UserDefaults.standard.set(self.index,
            forKey:"index")


        let app = UIApplication.shared()
        var taskId = UIBackgroundTaskInvalid
        let id = app.beginBackgroundTask() {
            print("Background task ran out of time and was terminated.")
            app.endBackgroundTask(taskId)
        }
        taskId = id


        if taskId == UIBackgroundTaskInvalid {
            print("Failed to start background task!")
            return
        }


        DispatchQueue.global(qos: .default).async {
                print("Starting background task with " +
                      "(app.backgroundTimeRemaining) seconds remaining")


                self.smiley = nil;
                self.smileyView.image = nil;


                // simulate a lengthy (25 seconds) procedure
                Thread.sleep(forTimeInterval: 25)


                print("Finishing background task with " +
                      "(app.backgroundTimeRemaining) seconds remaining")
                app.endBackgroundTask(taskId)
            });
    }

Let’s look through this code piece by piece. First, we grab the shared UIApplication instance , since we’ll be using it several times in this method. And then comes this:

        var taskId = UIBackgroundTaskInvalid
        let id = app.beginBackgroundTask() {
            print("Background task ran out of time and was terminated.")
            app.endBackgroundTask(taskId)
        }
        taskId = id

With the call to app.beginBackgroundTask(), we’re basically telling the system that we need more time to accomplish something, and we promise to let it know when we’re finished. The closure we give as a parameter may be called if the system decides that we’ve been going way too long anyway and decides to stop our background task. The call to app.beginBackgroundTask() returns an identifier that we save in the local variable taskId (if it better suits your class design, you could also store this value in a property of the view controller class).

Note that the closure ends with a call to endBackgroundTask(), passing along taskId. That tells the system that we’re finished with the work for which we previously requested extra time. It’s important to balance each call to app.beginBackgroundTask() with a matching call to endBackgroundTask() so that the system knows when we’ve completed the work.

Note

Depending on your computing background, the use of the word task here may evoke associations with what we usually call a process, consisting of a running program that may contain multiple threads, and so on. In this case, try to put that out of your mind. The use of task in this context really just means “something that needs to get done.” Any task you create here is running within your still-executing app.

Next, we do this:

        if taskId == UIBackgroundTaskInvalid {
            print("Failed to start background task!")
            return
        }

If our earlier call to app.beginBackgroundTask()returned the special value UIBackgroundTaskInvalid, which means the system is refusing to grant us any additional time. In that case, you could try to do the quickest part of whatever needs doing anyway and hope that it completes quickly enough that your app won’t be terminated before it’s finished. This was more likely to be an issue when running on older devices, such as the iPhone 3G, that didn’t support multitasking. In this example, however, we’re just letting it slide. Next comes the interesting part where the work itself is actually done:

        DispatchQueue.global(qos: .default).async {
                print("Starting background task with " +
                      "(app.backgroundTimeRemaining) seconds remaining")


                self.smiley = nil;
                self.smileyView.image = nil;


                // simulate a lengthy (25 seconds) procedure
                Thread.sleep(forTimeInterval: 25)


                print("Finishing background task with " +
                      "(app.backgroundTimeRemaining) seconds remaining")
                app.endBackgroundTask(taskId)
            });

All this does is take the same work our method was doing in the first place and place it in a background queue. Notice, though, that the code that uses UserDefaults to save state has not been moved into this closure. That’s because it’s important to save that state whether or not iOS grants the application additional time to run when it moves into the background. At the end of the closure, we call endBackgroundTask()to let the system know that we’re finished.

With that in place, build and run the app, and then background your app by pressing the home button. Watch the Xcode console and after 25 seconds, you will see the final log in your output. A complete run of the app up to this point should give you console output along these lines:

application(_:didFinishLaunchingWithOptions:)
applicationDidBecomeActive
VC: applicationDidBecomeActive()
applicationWillResignActive
VC: applicationWillResignActive()
applicationDidEnterBackground
VC: applicationDidEnterBackground()
Starting background task with 179.808078499991 seconds remaining
Finishing background task with 154.796897583336 seconds remaining

As you can see, the system is much more generous with time when doing things in the background than it is in the main thread of your app—in this example, it should give you a couple minutes to complete whatever you need to get done in the background. Following this procedure can really help you out if you have any ongoing tasks to deal with.

Note that we used only a single background task, but in practice, you can use as many as you need. For example, if you have multiple network transfers happening at Background time and you need to complete them, you can create a background task for each and allow them to continue running in a background queue. So, you can easily allow multiple operations to run in parallel during the available time.

Summary

This has been a pretty intense chapter, with a lot of new concepts thrown your way. You’ve discovered a new conceptual paradigm for dealing with concurrency without worrying about threads. Techniques for making sure your apps play nicely in the multitasking world of iOS were also demonstrated. Now that we’ve gotten some of this heavy stuff out of the way, let’s move on to the next chapter, which focuses on drawing.

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

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