© Radoslava Leseva Adams and Hristo Lesev 2016

Radoslava Leseva Adams and Hristo Lesev, Migrating to Swift from Flash and ActionScript, 10.1007/978-1-4842-1666-8_7

7. Concurrency

Radoslava Leseva Adams and Hristo Lesev2

(1)London, UK

(2)Kazanlak, Bulgaria

Imagine that you share a house with three housemates: Alice, Bob, and Charles; the house is a mess and you want it cleaned up for your mother’s visit on the weekend. You could do it all yourself, starting with the kitchen, moving on to the bathroom, then the hallway, and so on. Or you could recruit your housemates and give each of them tasks: Alice could tidy up the living room, Bob could vacuum it, and Charles could dust, while you water the plants, for example. Then all of you could get on with the kitchen, where you wash the dishes, Alice sweeps the floor, Bob takes out last night’s beer bottles to be recycled. . . . Or, better still: you give each housemate a room to clean up and leave them to it. That would work well with Alice’s sleeping until midday, Bob’s intolerance of Charles, and your fear of spiders (best to leave the bathroom to someone else). Your only worry is making sure there is no contention over shared resources like the vacuum cleaner . . .

Breaking up a job into tasks, which can be done more or less independently of one another, rather than in a particular order, makes a system concurrent. This makes sure that higher-priority tasks are not slowed down by waiting for lower-priority tasks to complete1 and even creates the possibility of tasks being done simultaneously and saving time.

In this chapter you will do the following:

  • learn about designing concurrent apps and the benefits and challenges this involves.

  • gain understanding of concurrency and multithreading concepts.

  • design and implement an app that does the same job in both concurrent and nonconcurrent ways, so you can contrast the two.

When you are done, you will have an app that searches for prime numbers in several different ways and also lets you cancel a lengthy search.

Understanding Concurrency

Concurrency is something that you need to consider at the design level of your app, before you start coding. Dividing functionality into tasks that can be run concurrently requires a lot more work and possibly a lot of code rewriting if done in an already existing code base than when you start fresh.

Benefits of Concurrency

Although it is not trivial, the effort of writing concurrent code usually pays off in one or more of the ways shown in the following list:

  • User interface responsiveness. It is best to keep computationally heavy work and user interface (UI) updates separate, so that the UI does not freeze while the heavy work is being done. Similarly, UI should be kept interactive and responsive while your app waits for a response from a remote server, for example.

  • Better resource utilization. In the last scenario waiting for a server response would keep the CPU (central processing unit) idle, unless your app can do other useful work in the meantime. For instance, instead of waiting for an entire audio file to download, in order to be played, your app could download a chunk of it and start playing it while the rest of the file is being downloaded.

  • Improved performance. On multicore systems, such as today’s iPhones and iPads, designing concurrent tasks makes it possible to run them in parallel, thus saving time. It is important to keep in mind that just because tasks can run at the same time does not mean that they will–more on that next.

Challenges of Concurrency

Designing concurrent apps has its flipside too–here are some of the challenges to keep in mind.

  • Parallelism is not guaranteed. Unless you are prepared to exercise very low-level control of scheduling, running, and synchronizing tasks, when and for how long each one runs is up to the operating system. Your job as a programmer is to write code that can be run in parallel if needed.

  • Improved performance is not a given either. It is worth measuring your app’s performance of critical jobs before and after you break them down into concurrent tasks. Too much granularity can often hurt, rather than improve speed.

  • Dealing with dependencies is important. Tasks designed to serve the same job are rarely completely independent of each other. It is especially important to consider scenarios where two or more tasks can get in a deadlock, each waiting for the other one to complete, in order to continue.

  • Managing shared resources is important. Tasks might need to access the same files, network connections, data in memory, or other resources. You need to make sure that this does not lead to data corruption, where more than one task modifies the same resource at the same time, or to inconsistencies, where reading data happens before writing to it has finished.

  • Debugging can be tricky. When there is more than one task running at the same time, it can be challenging to replicate and trace errors in your code, because how things happen is related to timing and order.

A few words on multithreading

Concurrency is traditionally linked with multithreaded programming. When making iOS apps you certainly have the option of accessing and using threads. Apple has also provided higher-level technologies, which can save you a lot of work related to thread scheduling and synchronization. We will use one of these technologies, Grand Central Dispatch (GCD), to build a concurrent app in the later part of this chapter. GCD itself uses threads, so it is worth understanding how these work, as this underpins GCD’s principles. Moreover, whether you write multithreaded code or not, your apps are multithreaded, as a lot of iOS SDK calls run their tasks on different threads.

When you start an app on your iOS device, it runs in a process. Each process is virtually independent of anything else that runs on the device: it has its own chunk of memory, which no other process can access and can pretend that it is the only thing running on the system.

Code in a process runs in one or more threads. You can think of a thread as a sequence of CPU instructions to be executed one after the other. A process has at least one thread of execution. When there are multiple threads, one of them is designated as the main thread. The main thread is where code that updates the UI usually runs. Other tasks can be assigned to different threads and be run at the same time or seemingly at the same time as the code on the main thread. Each thread has its own stack for instructions but does not have designated memory–threads share the same memory space.

Executing threads “seemingly at the same time” is what happens on single-core machines. As there is only one CPU, which can execute a single instruction at a time, it is impossible to have instructions run in parallel. Instead this is simulated: when a thread runs, a chunk of its instructions are executed, then the thread is put to sleep (i.e., paused), while a chunk of instructions from another thread are executed, then the second thread is put to sleep, so another one can run… This going to sleep and waking up require work: before it is paused, the running thread needs to save its state (its stack) and then needs to retrieve it when it is woken up, so it can continue from where it left off. This is called context switching and is one of the prices you pay for using multiple threads: saving and retrieving state takes additional time and memory. In the house-cleaning example this would be similar to the scenario, where all of your housemates have found better places to be and have left you to tidy up on your own. You could run multiple tasks at a time and try to wash the dishes and dust in parallel: you start the tap, put dishwashing liquid on the sponge, wash one dish, then put the sponge down, rinse your hands, turn the tap off, pick up the dust cloth and dust the top of the bookshelf; then start the tap again, put dishwashing liquid on the sponge. All of the preparation to wash a dish or to dust a shelf is what you do to switch contexts and in this case creates a lot of unnecessary overhead–you would probably be better off if you washed all of the dishes and then got on with dusting. Figure 7-1 illustrates this: threads running on a single-core CPU, rather than the house cleaning drama.

A371202_1_En_7_Fig1_HTML.jpg
Figure 7-1. Multiple threads running on a single-core machine

On multicore machines there is the option to run more than one task at a time (Figure 7-2): Alice can do the dishwashing, while you dust.

A371202_1_En_7_Fig2_HTML.jpg
Figure 7-2. Multiple threads running on a dual-core machine

When more than one thread can execute a piece of code, this code needs to be thread-safe. This means guaranteeing that there will be no unwanted side effects, such as data corruption or crashes, caused, for example, by a thread trying to access a variable that another thread has not yet finished initializing. Or Bob pulling the stepladder away just as you are about to climb on it to dust the chandelier.

Another couple of tricky situations that need to be considered with multithreading and when designing concurrent code in general are deadlocks and race conditions.

Deadlocking happens when two or more threads (or tasks) are paused and wait for each other to finish before they can continue execution. Say, Bob has kindly agreed to do the dishes and is waiting for you to return the dishwashing liquid, which you grabbed to treat a suspicious stain on the carpet. You, however, (prudently) want to use rubber gloves to do this, so you decide to wait until Bob has finished washing the dishes has and taken the gloves off.

A race condition typically arises when more than one thread or task can access and modify shared memory (or another resource) and the state of this memory can vary depending on who got to it first (i.e., it ends up being unpredictable). If two of your housemates decided to rearrange the bookshelves and Alice started ordering the books alphabetically, but Bob rearranged them by color and size at the same time,2 Alice and Bob would be in a race condition and the final state of the bookshelves would be anybody’s guess.

When you work with multiple threads, their scheduling is typically done by the operating system, but the communication between threads and ensuring their safety is up to you.

Apple’s GCD saves you some of this work by helping you think about your code in terms of tasks and queues of tasks, rather than low-level threads and their relation to hardware. In order to explore GCD and see what is available through it, we will design an app that uses it to accomplish a task concurrently. The next section will guide you through setting up the app.

Setting Up the App

The app we will use to demonstrate some of GCD’s concurrency techniques will find and display all prime numbers smaller than a number chosen by the user.

To start, create a new iOS Single View Application project in Xcode and name it FindPrimeNumbers (FileNewProject…, then iOSApplicationSingle View Application).

Open Main.storyboard and add three labels, a slider, a switch, a text view, a progress view, and two buttons. Figure 7-3 shows you how to lay out these UI elements and what names to give to the outlets you create for them in ViewController.swift. Don’t forget to constrain the layout, so that it adapts its size to different screens (see Chapter 5 for how to work with Auto Layout and Size Classes).

A371202_1_En_7_Fig3_HTML.jpg
Figure 7-3. Laying out the app screen on the storyboard

When the app is run, the user will move the slider to select a number and tap the Start button to get a list of all prime numbers smaller than the chosen one. Tapping the Cancel button will stop a search for prime numbers.

Some of the UI elements will need their properties set up in the Attributes inspector. The list below will guide you through doing this.

  • Select the text view, delete its default text under Text ViewText and uncheck Editable and Selectable under Text ViewBehavior.

  • Select the slider and set Minimum to 2, Maximum to 100, 000 and Current to 200 under SliderValue.

  • Select the progress view and set Progress ViewProgress to 0.

In ViewController.swift add actions for the Touch Up Inside events for both buttons and call them startSearch and cancelSearch, respectively. Then add an action for the Value Changed event of the slider and call it searchValueChanged.

The definition of ViewController should now look like Listing 7-1. We have added MARK annotations to organize the code and TODO annotations to help us plan what we need to do next. See Chapter 6 for a reminder about how these can help you navigate your source files more easily.

Listing 7-1. Adding Outlets and Actions to ViewController.swift
class ViewController: UIViewController {

    // MARK: Outlets
    @IBOutlet weak var resultsTextView: UITextView!
    @IBOutlet weak var searchLabel: UILabel!
    @IBOutlet weak var searchSlider: UISlider!
    @IBOutlet weak var cancelButton: UIButton!
    @IBOutlet weak var startButton: UIButton!
    @IBOutlet weak var concurrencySwitch: UISwitch!
    @IBOutlet weak var searchProgress: UIProgressView!


    // MARK: Actions
    @IBAction func searchValueChanged(sender: AnyObject) {
        // TODO: Update the search label to show the new value.
    }


    @IBAction func startSearch(sender: AnyObject) {
       // TODO: Reset any previous results shown on the screen.
       // TODO: Check if the concurrency switch is on.
       // TODO: Start a prime number search.
       // TODO: Enable the Cancel button if cancel is allowed.    
    }


    @IBAction func cancelSearch(sender: AnyObject) {
        // TODO: Cancel the search for prime numbers
        // TODO: Update the screen to show that the search was cancelled.  
        // TODO: Disable the Cancel button.             
    }


    // MARK: UIViewController
    override func viewDidLoad() {
        super.viewDidLoad()


        // TODO: Disable the Cancel button initially.
        // TODO: Make sure that the search label and the slider are in sync.
    }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

Let us take care of some of these TODOs, which we have left sprinkled around the code. First we will implement a method to keep the search label and the search slider in sync: when the user moves the slider, the label will update to show the new value that the slider was set to (Listing 7-2).

Listing 7-2. Updating the Search Label When the Slider Is Moved
// MARK: User interface helper methods
func updateSearchLabel() {
    searchLabel.text = "Find prime numbers less than
        (Int(searchSlider.value))"
}

Another couple of UI helper methods will take care of displaying the result from a search and resetting the screen before a new search begins (Listing 7-3). We will implement the search for prime numbers to produce an array of them, which can then be passed to the displayResults method shown in the listing.

Listing 7-3. Displaying and Resetting the Search Results
func displayResults(resultsArray primes: [UInt]) {
    if primes.isEmpty {
        resultsTextView.text = "No prime numbers were found."
        return
    }


    // Iterate through the primes array and construct a string,
    // which will be displayed in the text view:
    var resultStr = "(primes.count) prime numbers were found: "
    for i in primes.indices {
        resultStr += "Prime [(i + 1)] = (primes[i]) "
    }


    resultsTextView.text = resultStr

    // Reset the two buttons, so we can start another search:
    cancelButton.enabled = false
    startButton.enabled = true
}


func resetResults() {
    // Delete the results of the last search:
    resultsTextView.text = ""
    searchProgress.progress = 0.0
}
Swift reference

displayResults in Listing 7-3 takes an array of unsigned integers as a parameter. See Chapter 19 for information on arrays and other collection types in Swift.

Let us add one last method to the user interface helpers: this one will update the progress view and the text view to show the percentage of work done during a prime number search (Listing 7-4).

Listing 7-4. Showing Progress
func displayProgress(amountDone: Float) {
    searchProgress.progress = amountDone
    resultsTextView.text = "Progress: (Int(100.0 * amountDone))%"
}

Now let us put to use some of the methods we have just added. We will add calls to updateSearchLabel to the searchValueChanged action and to viewDidLoad. Inside viewDidLoad we will also disable the Cancel button (Listing 7-5).

Listing 7-5. Adding Calls to updateSearchLabel
// MARK: Actions
@IBAction func searchValueChanged(sender: AnyObject) {
    updateSearchLabel()
}


// MARK: UIViewController
override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view,
    // typically from a nib.


    updateSearchLabel()
    cancelButton.enabled = false
}

Next come a couple of functions that will help us with the search for prime numbers: findPrimeNubersInRange takes a range of unsigned integers and iterates through them: if a number is found to be prime, it gets added to an array, which the method then returns as a result. findPrimeNumbersInRange calls isItPrime–a method, which performs a brute-force3 check on a given number and returns true if the number is prime (Listing 7-6).

Listing 7-6. Defining Methods for Finding Prime Numbers
// MARK: Prime numbers helper methods
func isItPrime(number number: UInt) -> Bool {
    // We know that 0 and 1 are not prime:
    if number < 2 {
        return false
    }


    // Do a brute-force check for the rest of the numbers:
    for div: UInt in 2 ..< number {
        if (number % div) == 0 {
            return false
        }
    }


    return true
}


func findPrimeNumbersInRange(range r: Range<UInt>) -> [UInt] {
    // We will collect the results in this array:
    var primeNumbers = [UInt] ()


    for i in r {
        if isItPrime(number: i) {
            primeNumbers.append(i)
        }
    }


    return primeNumbers
}
Swift reference

The code in Listing 7-6 makes use of the Range type, which defines an interval of values. You can see how a range can be defined and iterated through in the for-in loop of the isItPrime method: 2 ..< number uses the half-open range operator to define a range starting at and including 2 and up to, but not including number. For details on Swift’s range operators see Chapter 18. For interesting ways of using them in cycles and switch statements see Chapter 20.

Run the app on a device or in Xcode’s simulator to test the UI. Does the search label update when you move the slider?

Next we will add two method placeholders: one to search for prime numbers and display the results sequentially and one that will use concurrency to do the same thing (Listing 7-7). Implementing and contrasting these methods are the main goals of this chapter.

Listing 7-7. Adding Placeholders for Trying Out Concurrent and Nonconcurrent Searches for Primes
// MARK: Search for prime numbers without concurrency
func findPrimeNumbersLessThan(number limit: UInt) {
    // Make sure we have been given a valid limit:
    assert(limit > 0)


    // TODO: Find all prime numbers smaller than limit and display them.
}


// MARK: Search for prime numbers, using concurrency
func concurrentlyFindPrimeNumbersLessThan(number limit: UInt) {
    // Make sure we have been given a valid limit:
    assert(limit > 0)


    // TODO: Find all prime numbers smaller than limit and display them.
}

Before we get on with implementing these two methods, let us add calls to them in the action we created for the Start buttons: find startSearch and replace the TODO annotations in it with the code from Listing 7-8.

Listing 7-8. Implementing the Start Button Action
@IBAction func startSearch(sender: AnyObject) {
    // Reset any previous results shown on the screen.
    resetResults()


    // Check if the concurrency switch is on
    // and start a search for prime numbers accordingly:
    if concurrencySwitch.on {
        concurrentlyFindPrimeNumbersLessThan(number UInt(searchSlider.value))


        // Enable the Cancel button, so we can cancel a concurrent search:
        cancelButton.enabled = true


        // Make sure we can't hit Start again before the search has completed:
        startButton.enabled = false


    } else {
        findPrimeNumbersLessThan(number: UInt(searchSlider.value))
    }
}

The app has been set up. Run it to make sure that there are no compile errors and then let us get on with implementing the main part of it: finding prime numbers in several different ways.

Nonconcurrent Implementation

We will first see how the search for prime numbers might work without any concurrency. The implementation of findPrimeNumbersLessThan is simple: obtain an array of prime numbers from findPrimeNumbersInRange and then pass it to displayResults (Listing 7-9).

Listing 7-9. Searching for Prime Numbers Without Employing Concurrency
// MARK: Search for prime numbers without concurrency
func findPrimeNumbersLessThan(number limit: UInt) {
    // Make sure we have been given a valid limit:
    assert(limit > 0)


    // Find all prime numbers smaller than limit:
    let primes = findPrimeNumbersInRange(range: 0...(limit - 1))


    // and then display them:
    displayResults(resultsArray: primes)
}

Run the app, make sure that the Use concurrency switch is turned off, and tap the Start button. You do not have to move the slider very far up before you notice that it takes a while for the results to appear and that the user interface freezes while the search is under way. In the next section we will see how splitting the work into concurrent tasks can help keep the user interface responsive, while a lengthy search is in progress.

Concurrent Implementation with GCD

GCD is one of the libraries that Apple has provided to help with concurrent programming. It adds an abstraction layer above raw threads and saves you the need to think about how threads and their interactions are managed. Instead, you are encouraged to imagine your app’s functionality as individual tasks, which can be placed on queues for execution. Each app (process) has a main queue, on which tasks end up by default, unless you dispatch them to a different queue to be done concurrently.

Note

Calls that modify the UI can only be executed on the main queue.

When choosing a queue to dispatch tasks to, we have the option of using one of the queues that iOS already has running, called global queues, or to create a custom one.

Queues can also be serial or concurrent. A serial queue waits for a task to finish before it starts the next one, so all tasks are guaranteed to start and finish in FIFO (first in, first out) order. The main queue is an example of a serial queue: tasks on it are executed one by one.

In contrast, a concurrent queue starts its tasks in FIFO order but does not wait for one task to finish before it can start the next one. So, if tasks A, B, and C were queued in that order, the queue will start A, start B, and finally start C. However the order in which they finish may be different: if task C is shorter, it may complete before A or B, for example.

Figure 7-4 shows you the difference between serial and concurrent queues.

A371202_1_En_7_Fig4_HTML.jpg
Figure 7-4. Contrasting serial and concurrent queues

It is worth mentioning that there is not necessarily a one-to-one correspondence between queues and threads. A serial queue will typically run one thread, but a concurrent queue might employ multiple threads to run its tasks. This is managed for you by GCD and you rarely have to think about the underlying threads in a queue.

With this in mind let us see how we can keep the UI responsive, while the search for prime numbers is going on in the background.

Performing a Search in the Background

A simple way of concurrently searching for prime numbers is to split the job into two tasks: one to find prime numbers and another one to show the results on the screen. The task of displaying the results will need to be part of the main queue, as it involves updating the UI. The search however can be performed on a concurrent queue. Listing 7-10 shows you the implementation, which we explain step by step below.

Listing 7-10. Adding the Search Task to a Background Queue
// MARK: Search for prime numbers, using concurrency
func concurrentlyFindPrimeNumbersLessThan(number limit: UInt) {
    // Make sure we have been given a valid limit:
    assert(limit > 0)


    // Obtain a reference to one of the existing queues:
    let queue =
        dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)


    // Create a block of code that will be added to the queue:
    let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {
        let primes = self.findPrimeNumbersInRange(range: 0...(limit - 1))


        // When the search is done,
        // obtain a reference to the main queue
        // and dispatch the task of updating the UI to it:
        let mainQueue = dispatch_get_main_queue()
        dispatch_async(mainQueue, {
            self.displayResults(resultsArray: primes)
        })
    }


    // Add the code block to the background queue,
    // so it can be scheduled for execution:
    dispatch_async(queue, block)
}

First we obtain a reference to one of the global queues, using the GCD function dispatch_get_global_queue and specifying an identifier for the queue we need. The queue identifier distinguishes between queues by the quality of service (QOS) they offer. Quality of service affects the priority with which tasks in the queue are run. How tasks are run also has an effect on the device’s battery life. You can normally choose between the following QOS classes4:

  • QOS_CLASS_USER_INTERACTIVE. Tasks with this QOS are treated as critical for keeping UI responsive and are thus executed with the highest priority for accessing system resources. They are the most energy-hungry.

  • QOS_CLASS_USER_INITIATED. Tasks on such a queue have usually been triggered by a user action and the user is assumed to be waiting for a result. They get a relatively high priority for execution but are not treated as critical.

  • QOS_CLASS_UTILITY. This QOS is for tasks that may or may not have been initiated by the user and have even lower priority. This allows them to be executed in a way that preserves energy (i.e., battery life).

  • QOS_CLASS_BACKGROUND. Tasks in background queues are run with the lowest priority and in the most energy-efficient way. This QOS is for doing work from which the user does not expect an immediate result and may not even be aware of altogether.

Our intent is for the user to start the search for prime numbers and keep the user interface responsive while he or she waits for the result, so we request a queue with QOS_CLASS_USER_INITIATED.

Next, we define the task that we will put on the queue and assign it to a constant called block. This is done by creating a dispatch block: a piece of code that can be assigned to a variable or a constant and be passed around as an argument to function calls.

Inside the code block we call the findPrimeNumbersInRange method we implemented earlier and when it returns its result, we create another task that will display it on the screen. We add this task to the main queue, as it needs to update the UI. Note that we can define a block of code inline without assigning it to a variable or a constant first–this is what the curly brackets in the call do:

dispatch_async(mainQueue, {/*Code for the task comes here*/})

Finally we call dispatch_async, which adds the task in block to the queue to be executed (Listing 7-10).

Swift reference

The code that is assigned to block and the code that is added to the main queue in Listing 7-10 have both been defined as closures and need to access methods and properties of ViewController through its implied property, called self. For more information on closures in Swift see Chapter 22 and for what the self property is see Chapter 21.

Run the app again and this time start the search for prime numbers with the Use concurrency switch turned on. There is an improvement: finding prime numbers still takes a while, but the app’s user interface does not freeze.

Displaying Progress

It would be nice to inform the user about how the search is going while he or she waits for the results. For that purpose we will first overload the findPrimesInRange function to report progress as it goes, which can then be displayed on the screen. We will have progress reported in a callback that is passed as an argument to the function.

On Listing 7-11 we first create a typealias (i.e., an alias for the type of callback we want to use), in this case a function that takes one argument of type Float and does not return a result. We then define findPrimeNumbersInRange to be almost like the original version we coded earlier, but with one extra argument that takes the callback, which is called with progress information as the search for prime numbers is going on.

Listing 7-11. Defining a Search Method That Reports Progress
// Define an alias for the callback
// we want to use to report progress:
typealias ProgressHandler = (Float) -> ()


func findPrimeNumbersInRange(range r: Range<UInt>,
    progressHandler: ProgressHandler) -> [UInt] {
    // We will collect the results in this array:
    var primeNumbers = [UInt] ()


    for i in r {
        if isItPrime(number: i) {
            primeNumbers.append(i)


            // Calculate the proportion of work done
            // and call the progress handler
            let progress = Float(i - r.first!) / Float(r.count)
            progressHandler(progress)
        }
    }


    // Job done:
    progressHandler(1.0)


    return primeNumbers
}
Swift reference

This version of findPrimeNumbersInRange is an overload of the original one: they both have the same name, but their signatures differ. When we call findPrimeNumbersInRange the compiler knows which version to use, based on the number of arguments we pass. More on overloading functions in Chapter 17 and on overloading operators in Chapter 18.

A quick note on syntax: we defined the typealias in Listing 7-11 for ease of reading, but this is not compulsory. Instead, you can declare findPrimeNumbersInRange like this by using the callback signature as the second argument’s type:

func findPrimeNumbersInRange(range r: Range<UInt>,
    progressHandler: ((Float) -> ())) -> [UInt]

All we need to do in our concurrentlyFindPrimeNumbersLessThan method is call the new overload of findPrimeNumbersInRange and pass it the displayProgress method we defined earlier; the rest of the code is exactly the same as it was (Listing 7-12).

Listing 7-12. Modifying concurrentlyFindPrimeNumbersLessThan to Use the New Search Method
// MARK: Search for prime numbers, using concurrency
func concurrentlyFindPrimeNumbersLessThan(number limit: UInt) {
    // Make sure we have been given a valid limit:
    assert(limit > 0)


    // Obtain a reference to one of the existing queues:
    let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)


    // Create a block of code that will be added to the queue:
    let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {
        let primes =
            self.findPrimeNumbersInRange(range: 0…(limit - 1),
                progressHandler: self.displayProgress)


        // When the search is done,
        // obtain a reference to the main queue
        // and dispatch the task of updating the UI to it:
        let mainQueue = dispatch_get_main_queue()
        dispatch_async(mainQueue, {
            self.displayResults(resultsArray: primes)
        })
    }


    // Add the code block to the background queue,
    // so it can be scheduled for execution:
    dispatch_async(queue, block)
}

Before you can safely run the app, however, there is something we need to do. Can you guess what it is? Have a look at Listing 7-13 to see if you guessed right.

Listing 7-13. Ensuring That Displaying Progress Happens on the Main Queue
func displayProgress(percent amountDone: Float) {
    dispatch_async(dispatch_get_main_queue(), {
        self.searchProgress.progress = amountDone
        self.resultsTextView.text = "Progress: (Int(100.0 * amountDone))%"
    })
}

The displayProgress method we defined earlier updates the UI, so we need to ensure that the update happens on the main queue. We have changed displayProgress and wrapped the UI modifications in a dispatch_async call that adds them to the main queue.

Controlling Access to Shared Resources with Dispatch Barriers

If we had to check a huge interval of numbers to find out which ones are prime, we could divide the job even further. Instead of having one task that iterates over all of the numbers, we could split the interval between several tasks–if run in parallel, they could, in theory, complete the job faster.

I hedged with throwing “in theory” there, as there is no universal recipe for improving performance. Making tasks more granular is a double-edged sword: it could speed up your code, but it could also just as easily slow it down if there is a lot of synchronization to do. Sharing resources between tasks is one tricky situation, which might require synchronization techniques that get in the way of performance.

In this section we will contrive a scenario to demonstrate how we can control access to shared resources between tasks with dispatch barriers. We will split the search for prime numbers into several tasks, which we will add to a queue to be run concurrently. But we will require that all results are combined in the same array in the end (and will not care if we end up with a sorted array of prime numbers or not). Access to this array will need to be limited to one task at a time within the queue–this is achieved by putting the code that modifies the array in a dispatch_barrier_async call.

Dispatch barriers are normally used to synchronize tasks in the same queue. The code in the barrier is executed only after all previous tasks that were submitted to the queue have finished. No other tasks are started before the barrier code completes.

We will do a few changes to concurrentlyFindPrimeNumbersLessThan to illustrate this. Listing 7-14 will guide you through these modifications.

Listing 7-14. Splitting the Search for Prime Numbers into Subtasks
// MARK: Search for prime numbers, using concurrency
func concurrentlyFindPrimeNumbersLessThan(number limit: UInt) {
    // Let us split the search into four:
    let concurrentTaskCount: UInt = 4
    // Based on the numer of concurrent tasks we want,
    // calculate the size of the interval
    // that each task will need to search through:
    let rangeSize = limit / concurrentTaskCount + limit % concurrentTaskCount


    // Keep track of how many tasks have completed:
    var tasksDone: UInt = 0


    // An array to combine results in the end:
    var primes = [UInt]()


    // Create a custom concurrent queue:
    let queue =
        dispatch_queue_create("com.diadraw.primeNumberSearchQueue",
            DISPATCH_QUEUE_CONCURRENT);


    for i in 0 ..< concurrentTaskCount {
        // Work out the interval for the search:
        let rangeStart = i * rangeSize
        if rangeStart >= limit {
            // If we have already covered the whole interval,
            // mark the task as complete:
            tasksDone += 1
            continue
        }
        let rangeEnd = min(UInt(rangeStart + rangeSize - 1), limit - 1)


        // Define a block of code to perform the search:
        let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {
            let primesInCurrentRange =
                self.findPrimeNumbersInRange(range: rangeStart ... rangeEnd)


            // Set a barrier around writing to the primes array,
            // so that no more than one task can write to it
            // at the same time:
            dispatch_barrier_async(queue, {
                primes.appendContentsOf(primesInCurrentRange)


                // Keep track of how many tasks have completed:
                tasksDone += 1


                // When we've got the last chunk of results in, display them:
                if tasksDone == concurrentTaskCount {
                    dispatch_async(dispatch_get_main_queue(), {
                        self.displayResults(resultsArray: primes)
                    })
                }
            })
        }


        // Add the block to the background queue:
        dispatch_async(queue, block)
    }
}

First we declare an array of unsigned integers, called primes, which will contain the results from all the tasks–this will be the shared resource between the queues. We also add a constant, concurrentTaskCount, which will determine into how many subtasks we want to split the search for prime numbers –let us arbitrarily set it to 4. In a loop from zero to concurrentTaskCount we do mostly the same as before: define a code block, which calls findPrimeNumbersInRange and receives from it an array of prime numbers. This time we will have several blocks, each of which does the search in its own subinterval of numbers. Instead of displaying the results as soon as they arrive, however, each task will add its results to the primes array and only when all tasks are done will we send the results to the main queue to be displayed on the screen. This is the code we put in a dispatch barrier.

Something to note about dispatch barriers is that they only make sense to use with custom concurrent queues. As a barrier serializes access to a resource, it will not make a difference on a serial queue. Put on a global queue, barriers are likely to be ignored: the priority of iOS’s scheduler is to keep things running and not allow your app to block the execution of other processes. For that reason, instead of requesting a reference to a global queue like we did earlier, Listing 7-14 creates a custom queue with dispatch_queue_create.

In this version of concurrentlyFindPrimeNumbersLessThan we have opted out of displaying progress to keep things simple.

Cancelling Lengthy Tasks

It is nice to allow the user to cancel a task that can take a while, especially if the user managed to start it accidentally or with the wrong input. You know the moment of “Oh, £@%^. I meant to download the latest Xcode, not all of the ‘X-Men’ series. . . .”

Let us modify the concurrentlyFindPrimeNumbersLessThan method, so that tasks can be cancelled. In order to do that, we will need access to the blocks of code that we add to the queue outside of concurrentlyFindPrimeNumbersLessThan. To help with that we will define an array to keep references to them. Add this array, called blocks, as a property to ViewController (Listing 7-15).

Listing 7-15. Defining an Array to Keep References to the Code Blocks That Are Running Concurrently
// MARK: Search for prime numbers, using concurrency
// An array of execution blocks:
var blocks = [dispatch_block_t]()

The only modification we need to make to concurrentlyFindPrimeNumbersLessThan is at the end of the for-in cycle that creates and adds each code block to the queue. Find this line,

dispatch_async(queue, block)

and replace it with the code after the last comment in Listing 7-16. In this new version we first add a reference to the block of code in the blocks array, so we can access it for cancellation later. Then we send it to the concurrent queue, but instead of dispatch_async, we call dispatch_after. This call delays the execution of the block with a given time interval, specified in nanoseconds. Note that this is not a guarantee that the task in the block will be executed exactly after that long but only that it will not start before the time interval has passed.

We have added this delay, so we can test the cancellation. One thing to keep in mind when cancelling tasks in a queue is that they can only be cancelled before they have started executing. Cancelling a task that is already running has no effect. The five-second delay we have added should give us enough time to tap the Cancel button and see the effect. To reflect that, a nice addition to the UI would be to disable the Cancel button once the concurrent tasks start executing. We have omitted this from Listing 7-16, in order to keep it simple, and are leaving it as a little homework to you.

Listing 7-16. Modifying concurrentlyFindPrimeNumbersLessThan, So That We Can Test Cancelling Tasks
func concurrentlyFindPrimeNumbersLessThan(number limit: UInt) {
    // Make sure we clear the blocks array from any old task references:
    blocks.removeAll()
    // The rest of the code below this line stays the same.


    for i in 0 ..< concurrentTaskCount {
        // The rest of the code in the for-in loop stays the same.


        // Add the block to the queue, but after a delay:
        blocks.append(block)


        let delayInSeconds = 5.0
        let startTime = dispatch_time(DISPATCH_TIME_NOW,
            Int64(delayInSeconds * Double(NSEC_PER_SEC)))
        dispatch_after(startTime, queue, block)
    }
}

Now let us implement the actual cancellation. Find the cancelSearch action we added earlier and replace the TODO annotations inside it with the code from Listing 7-17. As you can see, cancelling a block of code is quite simple–it is done with a call to dispatch_block_cancel.

Listing 7-17. Cancelling the Execution of Blocks of Code
@IBAction func cancelSearch(sender: AnyObject) {
    // Cancel any blocks that have not been run yet:
    for block in blocks {
        dispatch_block_cancel(block)
    }


    // Update the screen to show that the search was cancelled.
    resultsTextView.text.appendContentsOf(" Search cancelled.")


    // Disable Cancel, but enable Start:
    cancelButton.enabled = false
    startButton.enabled = true
}

Run the app, set the search limit with the slider, and tap the Start button. If you tap the Cancel button within five seconds after that, you should be able to cancel the search.

If You Do Need to Use Threads

You might find yourself in a situation where you do need lower-level control over the concurrency in your apps. Apple’s Threading Programming Guide, available online at https://goo.gl/A2Z7zh , will help you get familiar with and start using threads on iOS with Swift.

Measure, Measure, Measure

A parting word on concurrency: when doing optimizations in your code, it is always a good idea to measure its performance before and after making changes. What sometimes seems like a more efficient way of doing things can surprise you by slowing your app down or making your UI appear less responsive than before. For example, you may find that while the search for prime numbers is going on in the background, trying to report each prime number as it is found may not work very well, as scheduling UI updates is not entirely up to you. This perceptively hurts performance: not only is more time spent constructing strings and sending them to the text view to be shown but also strings don’t necessarily appear when you want them to.

While we are at it, here are some observations and measurements from the various versions of concurrentlyFindPrimeNumbersLessThan that we implemented in this chapter. I timed how long it took to find all prime numbers smaller than 100, 000 on an iPad 3:

  • The version, in Listing 7-10, which defines one task to search for prime numbers in the background did its job in about 80 seconds.

  • The version, in Listing 7-14, which runs four tasks to do the same job took about 40 seconds.

  • Splitting the job between more tasks hardly gains us any improvement at all: 10 tasks took 37 seconds and 100 took 34 seconds.

The initial 50% increase in speed is better than nothing, but it may not be enough to justify making code more complex.

Summary

In this chapter we covered some of the techniques and abstractions that Apple has provided to help Swift developers design concurrent applications. We outlined the pros and cons of writing concurrent code and put it to the test with the help of GCD. We hope it will be a good starting point for your exploration of this wide and varied subject.

In the next chapter we will look at some of the tools that Swift and Xcode can offer in order to help you write better and more robust code.

Footnotes

1 Your mother may never even peek into Charles’s room, but the living room needs to be spotless.

2 Considering that you grew up with a mother whose visit requires such levels of preparations, it is little wonder that you ended up with pedantic housemates. Sorry.

3 A number is prime if it is greater than 1 and has no positive divisors other than 1 and itself. isItPrime naively iterates through the interval [2, number)–that is, starting at and including 2 and up to, but not including, number–to check if number has any divisors in it.

4 The word “class” here comes from “classification” and is not to be confused with the Object-Oriented concept.

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

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