NSOperation and NSOperationQueue in Swift

Tasks, Threads and Processes

There are a few technical concepts that need to be dealt with. I’m going to define a few terms:

  • Task: a simple, single piece of work that needs to be done.
  • Thread: a mechanism provided by the operating system that allows multiple sets of instructions to operate at the same time within a single application.
  • Process: an executable chunk of code, which can be made up of multiple threads.

Note: In iOS and OS X, the threading functionality is provided by the POSIX Threads API (or pthreads), and is part of the operating system. This is pretty low level stuff, and you will find that it is easy to make mistakes; perhaps the worst thing about threads is those mistakes can be incredibly hard to find!

The Foundation framework contains a class called NSThread, which is much easier to deal with, but managing multiple threads with NSThread is still a headache. NSOperation and NSOperationQueue are higher level classes that have greatly simplified the process of dealing with multiple threads.

In this one can performs the work of reading a file, while the other thread performs user-interface related code. This is quite similar to how you should structure your code in iOS – the main thread should perform any work related to the user interface, and secondary threads should perform slow or long-running operations such as reading files, acccessing the network, etc.

NSOperation vs. Grand Central Dispatch (GCD)

You may have heard of Grand Central Dispatch (GCD). In a nutshell, GCD consists of language features, runtime libraries, and system enhancements to provide systemic and comprehensive improvements to support concurrency on multi-core hardware in iOS and OS X.

NSOperation and NSOperationQueue are built on top of GCD. As a very general rule, Apple recommends using the highest-level abstraction, and then dropping down to lower levels when measurements show they are needed.

Here’s a quick comparison of the two that will help you decide when and where to use GCD or NSOperation:

  • GCD is a lightweight way to represent units of work that are going to be executed concurrently. You don’t schedule these units of work; the system takes care of scheduling for you. Adding dependency among blocks can be a headache. Canceling or suspending a block creates extra work for you as a developer! :]
  • NSOperation adds a little extra overhead compared to GCD, but you can add dependency among various operations and re-use, cancel or suspend them.

This tutorial will use NSOperation because you’re dealing with a table view and for performance and power consumption reasons you need to be able to cancel an operation for a specific image if the user has scrolled that image off the screen. Even if the operations are on a background thread, if there are dozens of them waiting on the queue then performance will still suffer.

Redefined App Model

To get rid of your application bottlenecks, you’ll need a thread specifically to respond to user interactions, a thread dedicated to downloading data source and images, and a thread for performing image filtering. In the new model, the app starts on the main thread and loads an empty table view. At the same time, the app launches a second thread to download the data source.

Once the data source has been downloaded, you’ll tell the table view to reload itself. This has to be done on the main thread, since it involves the user interface. At this point, the table view knows how many rows it has, and it knows the URL of the images it needs to display, but it doesn’t have the actual images yet! If you immediately started to download all images at this point, it would be terribly inefficient, as you don’t need all the images at once!

What can be done to make this better?

A better model is just to start downloading the images whose respective rows are visible on the screen. So your code will first ask the table view which rows are visible, and only then will it start the download process. As well, the image filtering process can’t be started before the image is completely downloaded. Therefore, the code should not start the image filtering process until there is an unfiltered image waiting to be processed.

To make the app appear more responsive, the code will display the image right away once it is downloaded. It will then kick off the image filtering, and then update the UI to display the filtered image. The diagram below shows the schematic control flow for this process:

To achieve these objectives, you will need to track whether the image is currently being downloaded, is finished being downloaded, or if the image filtering has been applied. You will also need to track the status of each operation, and whether it is a downloading or filtering operation, so that you can cancel, pause or resume each as the user scrolls.

import UIKit

// This enum contains all the possible states a photo record can be in
enum PhotoRecordState {
  case New, Downloaded, Filtered, Failed
}

class PhotoRecord {
  let name:String
  let url:NSURL
  var state = PhotoRecordState.New
  var image = UIImage(named: "Placeholder")

  init(name:String, url:NSURL) {
    self.name = name
    self.url = url
  }
}

This simple class will represent each photo displayed in the app, together with its current state, which defaults to .New for newly created records. The image defaults to a placeholder.

To track the status of each operation, you’ll need a separate class.

class PendingOperations {
  lazy var downloadsInProgress = [NSIndexPath:NSOperation]()
  lazy var downloadQueue:NSOperationQueue = {
    var queue = NSOperationQueue()
    queue.name = "Download queue"
    queue.maxConcurrentOperationCount = 1
    return queue
    }()
  
  lazy var filtrationsInProgress = [NSIndexPath:NSOperation]()
  lazy var filtrationQueue:NSOperationQueue = {
    var queue = NSOperationQueue()
    queue.name = "Image Filtration queue"
    queue.maxConcurrentOperationCount = 1
    return queue
    }()
}

This class contains two dictionaries to keep track of active and pending download and filter operations for each row in the table, and two operation queues for each type of operation.

All of the values are created lazily, meaning they aren’t initialized until they are first accessed. This improves the performance of your app.

Creating an NSOperationQueue is very straightforward, as you can see. Naming your queues helps, since the names show up in Instruments or the debugger. The maxConcurrentOperationCount is set to 1 here for the sake of this tutorial, to allow you to see operations finishing one by one. You could leave this part out to allow the queue to decide how many operations it can handle at once – this would further improve performance.

How does the queue decide how many operations it can run at once? That’s a good question! :] It depends on the hardware. By default, NSOperationQueue will do some calculation behind the scenes, decide what is best for the particular platform the code is running on, and will launch the maximum possible number of threads.

Consider the following example. Assume the system is idle, and there are lots of resources available, so the queue could launch something like eight simultaneous threads. Next time you run the program, the system could be busy with other unrelated operations which are consuming resources, and the queue will only launch two simultaneous threads. Because you’ve set a maximum concurrent operations count, in this app only one operation will happen at a time.

Note:You might wonder why you have to keep track of all active and pending operations. The queue has an operations method which returns an array of operations, so why not use that? In this project it won’t be very efficient to do so. You need to track which operations are associated with which table view rows, which would involve iterating over the array each time you needed one. Storing them in a dictionary with the index path as a key means lookup is fast and efficient.

It’s time to take care of download and filtration operations.

class ImageDownloader: NSOperation {
  //1
  let photoRecord: PhotoRecord

  //2
  init(photoRecord: PhotoRecord) {
    self.photoRecord = photoRecord
  }

  //3
  override func main() {
    //4
    if self.cancelled {
      return
    }
    //5
    let imageData = NSData(contentsOfURL:self.photoRecord.url)

    //6
    if self.cancelled {
      return
    }

    //7
    if imageData?.length > 0 {
      self.photoRecord.image = UIImage(data:imageData!)
      self.photoRecord.state = .Downloaded
    }
    else
    {
      self.photoRecord.state = .Failed
      self.photoRecord.image = UIImage(named: "Failed")
    }
  }
}

NSOperation is an abstract class, designed for subclassing. Each subclass represents a specific task as represented earlier.

Here’s what’s happening at each of the numbered comments in the code above:

  1. Add a constant reference to the PhotoRecord object related to the operation.
  2. Create a designated initializer allowing the photo record to be passed in.
  3. main is the method you override in NSOperation subclasses to actually perform work.
  4. Check for cancellation before starting. Operations should regularly check if they have been cancelled before attempting long or intensive work.
  5. Download the image data.
  6. Check again for cancellation.
  7. If there is data, create an image object and add it to the record, and move the state along. If there is no data, mark the record as failed and set the appropriate image.

Next, you’ll create another operation to take care of image filtering!

class ImageFiltration: NSOperation {
  let photoRecord: PhotoRecord

  init(photoRecord: PhotoRecord) {
    self.photoRecord = photoRecord
  }

  override func main () {
    if self.cancelled {
      return
    }

    if self.photoRecord.state != .Downloaded {
      return
    }

    if let filteredImage = self.applySepiaFilter(self.photoRecord.image!) {
      self.photoRecord.image = filteredImage
      self.photoRecord.state = .Filtered
    }
  }
}

This looks very similar to the downloading operation, except that you’re applying a filter to the image (using an as yet unimplemented method, hence the compiler error) instead of downloading it.

Add the missing image filter method to the ImageFiltration class:

func applySepiaFilter(image:UIImage) -> UIImage? {
  let inputImage = CIImage(data:UIImagePNGRepresentation(image))

  if self.cancelled {
    return nil
  }
  let context = CIContext(options:nil)
  let filter = CIFilter(name:"CISepiaTone")
  filter.setValue(inputImage, forKey: kCIInputImageKey)
  filter.setValue(0.8, forKey: "inputIntensity")
  let outputImage = filter.outputImage

  if self.cancelled {
    return nil
  }

  let outImage = context.createCGImage(outputImage, fromRect: outputImage.extent())
  let returnImage = UIImage(CGImage: outImage)
  return returnImage
}

The image filtering is the same implementation used previously in ListViewController. It’s been moved here so that it can be done as a separate operation in the background. Again, you should check for cancellation very frequently; a good practice is to do it before and after any expensive method call. Once the filtering is done, you set the values of the photo record instance.

Great! Now you have all the tools and foundation you need in order to process operations as a background tasks. It’s time to go back to the view controller and modify it to take advantage of all these new benefits.

Delete the lazy var photos property declaration. Add the following declarations instead:

var photos = [PhotoRecord]()
let pendingOperations = PendingOperations()

These will hold an array of the PhotoDetails objects you created earlier, and the PendingOperations object to manage the operations.

Add a new method to the class to download the photos property list:

func fetchPhotoDetails() {
  let request = NSURLRequest(URL:dataSourceURL!)
  UIApplication.sharedApplication().networkActivityIndicatorVisible = true
  
  NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {response,data,error in
    if data != nil {
      let datasourceDictionary = NSPropertyListSerialization.propertyListWithData(data, options: Int(NSPropertyListMutabilityOptions.Immutable.rawValue), format: nil, error: nil) as! NSDictionary
      
      for(key : AnyObject,value : AnyObject) in datasourceDictionary {
        let name = key as? String
        let url = NSURL(string:value as? String ?? "")
        if name != nil && url != nil {
          let photoRecord = PhotoRecord(name:name!, url:url!)
          self.photos.append(photoRecord)
        }
      }
      
      self.tableView.reloadData()
    }
    
    if error != nil {
      let alert = UIAlertView(title:"Oops!",message:error.localizedDescription, delegate:nil, cancelButtonTitle:"OK")
      alert.show()
    }
    UIApplication.sharedApplication().networkActivityIndicatorVisible = false
  }
}

This method creates an asynchronous web request which, when finished, will run the completion block on the main queue. When the download is complete the property list data is extracted into an NSDictionaryand then processed again into an array of PhotoRecord objects. You haven’t used an NSOperation directly here, but instead you’ve accessed the main queue using NSOperationQueue.mainQueue().

Call the new method at the end of viewDidLoad:

fetchPhotoDetails()

Next, find tableView(_:cellForRowAtIndexPath:) and replace it with the following implementation:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("CellIdentifier", forIndexPath: indexPath) as! UITableViewCell
  
  //1
  if cell.accessoryView == nil {
    let indicator = UIActivityIndicatorView(activityIndicatorStyle: .Gray)
    cell.accessoryView = indicator
  }
  let indicator = cell.accessoryView as! UIActivityIndicatorView
  
  //2
  let photoDetails = photos[indexPath.row]
  
  //3
  cell.textLabel?.text = photoDetails.name
  cell.imageView?.image = photoDetails.image
  
  //4
  switch (photoDetails.state){
  case .Filtered:
    indicator.stopAnimating()
  case .Failed:
    indicator.stopAnimating()
    cell.textLabel?.text = "Failed to load"
  case .New, .Downloaded:
    indicator.startAnimating()
    self.startOperationsForPhotoRecord(photoDetails,indexPath:indexPath)
  }
  
  return cell
}

Take some time to read through the explanation of the commented sections below:

  1. To provide feedback to the user, create a UIActivityIndicatorView and set it as the cell’s accessory view.
  2. The data source contains instances of PhotoRecord. Fetch the right one based on the current row’s indexPath.
  3. The cell’s text label is (nearly) always the same and the image is set appropriately on the PhotoRecordas it is processed, so you can set them both here, regardless of the state of the record.
  4. Inspect the record. Set up the activity indicator and text as appropriate, and kick off the operations (not yet implemented)

You can remove the implementation of applySepiaFilter since that won’t be called any more. Add the following method to the class to start the operations:

func startOperationsForPhotoRecord(photoDetails: PhotoRecord, indexPath: NSIndexPath){
  switch (photoDetails.state) {
  case .New:
    startDownloadForRecord(photoDetails, indexPath: indexPath)
  case .Downloaded:
    startFiltrationForRecord(photoDetails, indexPath: indexPath)
  default:
    NSLog("do nothing")
  }
}

Here, you’ll pass in an instance of PhotoRecord along with its index path. Depending on the photo record’s state, you kick off either the download or filter steps.

Note: the methods for downloading and filtering images are implemented separately, as there is a possibility that while an image is being downloaded the user could scroll away, and you won’t yet have applied the image filter. So next time the user comes to the same row, you don’t need to re-download the image; you only need to apply the image filter! Efficiency rocks! :]

Now you need to implement the methods that you called in the method above. Remember that you created a custom class, PendingOperations, to keep track of operations; now you actually get to use it! Add the following methods to the class:

func startDownloadForRecord(photoDetails: PhotoRecord, indexPath: NSIndexPath){
  //1
  if let downloadOperation = pendingOperations.downloadsInProgress[indexPath] {
    return
  }

  //2
  let downloader = ImageDownloader(photoRecord: photoDetails)
  //3
  downloader.completionBlock = {
    if downloader.cancelled {
      return
    }
    dispatch_async(dispatch_get_main_queue(), {
      self.pendingOperations.downloadsInProgress.removeValueForKey(indexPath)
      self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    })
  }
  //4
  pendingOperations.downloadsInProgress[indexPath] = downloader
  //5
  pendingOperations.downloadQueue.addOperation(downloader)
}

func startFiltrationForRecord(photoDetails: PhotoRecord, indexPath: NSIndexPath){
  if let filterOperation = pendingOperations.filtrationsInProgress[indexPath]{
    return
  }

  let filterer = ImageFiltration(photoRecord: photoDetails)
  filterer.completionBlock = {
    if filterer.cancelled {
      return
    }
    dispatch_async(dispatch_get_main_queue(), {
      self.pendingOperations.filtrationsInProgress.removeValueForKey(indexPath)
      self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
      })
  }
  pendingOperations.filtrationsInProgress[indexPath] = filterer
  pendingOperations.filtrationQueue.addOperation(filterer)
}

Okay! Here’s a quick list to make sure you understand what’s going on in the code above:

  1. First, check for the particular indexPath to see if there is already an operation in downloadsInProgressfor it. If so, ignore it.
  2. If not, create an instance of ImageDownloader by using the designated initializer.
  3. Add a completion block which will be executed when the operation is completed. This is a great place to let the rest of your app know that an operation has finished. It’s important to note that the completion block is executed even if the operation is cancelled, so you must check this property before doing anything. You also have no guarantee of which thread the completion block is called on, so you need to use GCD to trigger a reload of the table view on the main thread.
  4. Add the operation to downloadsInProgress to help keep track of things.
  5. Add the operation to the download queue. This is how you actually get these operations to start running – the queue takes care of the scheduling for you once you’ve added the operation.

The method to filter the image follows the same pattern, except it uses ImageFiltration and filtrationsInProgress to track the operations. As an exercise, you could try getting rid of the repetition in this section of code :]

You made it! Your project is complete.

Isn’t that cool? You can see how a little effort can go a long way towards making your applications a lot more responsive — and a lot more fun for the user!

Fine tuning

You’ve come a long way in this tutorial! Your little project is responsive and shows lots of improvement over the original version. However, there are still some small details that are left to take care of. You want to be a great programmer, not just a good one!

You may have noticed that as you scroll away in table view, those offscreen cells are still in the process of being downloaded and filtered. If you scroll quickly, the app will be busy downloading and filtering images from the cells further back in the list even though they aren’t visible. Ideally, the app should cancel filtering of off-screen cells and prioritize the cells that are currently displayed.

Didn’t you put cancellation provisions in your code? Yes, you did — now you should probably make use of them! :]

Go back to Xcode, and open ListViewController.swift. Go to the implementation of tableView(_:cellForRowAtIndexPath:), and wrap the call to startOperationsForPhotoRecord in an if-clause as follows:

if (!tableView.dragging && !tableView.decelerating) {
  self.startOperationsForPhotoRecord(photoDetails, indexPath: indexPath)
}

You tell the table view to start operations only if the table view is not scrolling. These are actually properties of UIScrollView, and because UITableView is a subclass of UIScrollView, you automatically inherit these properties.

Next, add the implementation of the following UIScrollView delegate methods to the class:

override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
  //1
  suspendAllOperations()
}

override func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  // 2
  if !decelerate {
    loadImagesForOnscreenCells()
    resumeAllOperations()
  }
}

override func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
  // 3
  loadImagesForOnscreenCells()
  resumeAllOperations()
}

A quick walkthrough of the code above shows the following:

  1. As soon as the user starts scrolling, you will want to suspend all operations and take a look at what the user wants to see. You will implement suspendAllOperations in just a moment.
  2. If the value of decelerate is false, that means the user stopped dragging the table view. Therefore you want to resume suspended operations, cancel operations for offscreen cells, and start operations for onscreen cells. You will implement loadImagesForOnscreenCells and resumeAllOperations in a little while as well.
  3. This delegate method tells you that table view stopped scrolling, so you will do the same as in #2.

Now, add the implementation of these missing methods:

func suspendAllOperations () {
  pendingOperations.downloadQueue.suspended = true
  pendingOperations.filtrationQueue.suspended = true
}

func resumeAllOperations () {
  pendingOperations.downloadQueue.suspended = false
  pendingOperations.filtrationQueue.suspended = false
}

func loadImagesForOnscreenCells () {
  //1
  if let pathsArray = tableView.indexPathsForVisibleRows() {
    //2
    var allPendingOperations = Set(pendingOperations.downloadsInProgress.keys.array)
    allPendingOperations.unionInPlace(pendingOperations.filtrationsInProgress.keys.array)
    
    //3
    var toBeCancelled = allPendingOperations
    let visiblePaths = Set(pathsArray as! [NSIndexPath])
    toBeCancelled.subtractInPlace(visiblePaths)
    
    //4
    var toBeStarted = visiblePaths
    toBeStarted.subtractInPlace(allPendingOperations)
    
    // 5
    for indexPath in toBeCancelled {
      if let pendingDownload = pendingOperations.downloadsInProgress[indexPath] {
        pendingDownload.cancel()
      }
      pendingOperations.downloadsInProgress.removeValueForKey(indexPath)
      if let pendingFiltration = pendingOperations.filtrationsInProgress[indexPath] {
        pendingFiltration.cancel()
      }
      pendingOperations.filtrationsInProgress.removeValueForKey(indexPath)
    }
    
    // 6
    for indexPath in toBeStarted {
      let indexPath = indexPath as NSIndexPath
      let recordToProcess = self.photos[indexPath.row]
      startOperationsForPhotoRecord(recordToProcess, indexPath: indexPath)
    }
  }
}

suspendAllOperations and resumeAllOperations have a straightforward implementation. NSOperationQueues can be suspended, by setting the suspended property to true. This will suspend all operations in a queue — you can’t suspend operations individually.

loadImagesForOnscreenCells is a little more complex. Here’s what’s going on:

  1. Start with an array containing index paths of all the currently visible rows in the table view.
  2. Construct a set of all pending operations by combining all the downloads in progress + all the filters in progress.
  3. Construct a set of all index paths with operations to be cancelled. Start with all operations, and then remove the index paths of the visible rows. This will leave the set of operations involving off-screen rows.
  4. Construct a set of index paths that need their operations started. Start with index paths all visible rows, and then remove the ones where operations are already pending.
  5. Loop through those to be cancelled, cancel them, and remove their reference from PendingOperations.
  6. Loop through those to be started, and call startOperationsForPhotoRecord for each.
NSOperation and NSOperationQueue in Swift

GCD(Grand Central Dispatch) Threading for Swift 3

GCD Concepts

To understand GCD, you need to be comfortable with several concepts related to concurrency and threading.

Concurrency

In iOS a process or application is made up of one or more threads. The threads are managed independently by the operating system scheduler. Each thread can execute concurrently but it’s up to the system to decide if this happens and how it happens.

Single-core devices can achieve concurrency through time-slicing. They would run one thread, perform a context switch, then run another thread.

Concurrency_vs_Parallelism

Multi-core devices on the other hand, execute multiple threads at the same time via parallelism.

GCD is built on top of threads. Under the hood it manages a shared thread pool. With GCD you add blocks of code or work items to dispatch queues and GCD decides which thread to execute them on.

As you structure your code, you’ll find code blocks that can run simultaneously and some that should not. This then allows you to use GCD to take advantage of concurrent execution.

Note that GCD decides how much parallelism is required based on the system and available system resources. It’s important to note that parallelism requires concurrency, but concurrency does not guarantee parallelism.

Basically, concurrency is about structure while parallelism is about execution.

Queues

GCD provides dispatch queues represented by DispatchQueue to manage tasks you submit and execute them in a FIFO order guaranteeing that the first task submitted is the first one started.

Dispatch queues are thread-safe which means that you can access them from multiple threads simultaneously. The benefits of GCD are apparent when you understand how dispatch queues provide thread safety to parts of your own code. The key to this is to choose the right kind of dispatch queue and the right dispatching function to submit your work to the queue.

Queues can be either serial or concurrent. Serial queues guarantee that only one task runs at any given time. GCD controls the execution timing. You won’t know the amount of time between one task ending and the next one beginning:

Serial-Queue-Swift

Concurrent queues allow multiple tasks to run at the same time. Tasks are guaranteed to start in the order they were added. Tasks can finish in any order and you have no knowledge of the time it will take for the next task to start, nor the number of tasks that are running at any given time.

See the sample task execution below:Concurrent-Queue-Swift

 

Notice how Task 1, Task 2, and Task 3 start quickly one after the other. On the other hand, Task 1 took a while to start after Task 0. Also notice that while Task 3 started after Task 2, it finished first.

The decision of when to start a task is entirely up to GCD. If the execution time of one task overlaps with another, it’s up to GCD to determine if it should run on a different core, if one is available, or instead to perform a context switch to run a different task.

GCD provides three main types of queues:

  1. Main queue: runs on the main thread and is a serial queue.
  2. Global queues: concurrent queues that are shared by the whole system. There are four such queues with different priorities : high, default, low, and background. The background priority queue is I/O throttled.
  3. Custom queues: queues that you create which can be serial or concurrent. These actually trickle down into being handled by one of the global queues.

When setting up the global concurrent queues, you don’t specify the priority directly. Instead you specify a Quality of Service (QoS) class property. This will indicate the task’s importance and guide GCD into determining the priority to give to the task.

The QoS classes are:

  • User-interactive: This represents tasks that need to be done immediately in order to provide a nice user experience. Use it for UI updates, event handling and small workloads that require low latency. The total amount of work done in this class during the execution of your app should be small. This should run on the main thread.
  • User-initiated: The represents tasks that are initiated from the UI and can be performed asynchronously. It should be used when the user is waiting for immediate results, and for tasks required to continue user interaction. This will get mapped into the high priority global queue.
  • Utility: This represents long-running tasks, typically with a user-visible progress indicator. Use it for computations, I/O, networking, continous data feeds and similar tasks. This class is designed to be energy efficient. This will get mapped into the low priority global queue.
  • Background: This represents tasks that the user is not directly aware of. Use it for prefetching, maintenance, and other tasks that don’t require user interaction and aren’t time-sensitive. This will get mapped into the background priority global queue.

Synchronous vs. Asynchronous

With GCD, you can dispatch a task either synchronously or asynchronously.

A synchronous function returns control to the caller after the task is completed.

An asynchronous function returns immediately, ordering the task to be done but not waiting for it. Thus, an asynchronous function does not block the current thread of execution from proceeding on to the next function.

Handling Background Tasks

 

DispatchQueue.global(qos: .userInitiated).async { // 1
  let overlayImage = self.faceOverlayImageFromImage(self.image)
  DispatchQueue.main.async { // 2
    self.fadeInNewImage(overlayImage) // 3
  }
}

Here’s what the code’s doing step by step:

  1. You move the work to a background global queue and run the work in the closure asynchronously. This lets viewDidLoad() finish earlier on the main thread and makes the loading feel more snappy. Meanwhile, the face detection processing is started and will finish at some later time.
  2. At this point, the face detection processing is complete and you’ve generated a new image. Since you want to use this new image to update your UIImageView, you add a new closure to the main queue. Remember – you must always access UIKit classes on the main thread!
  3. Finally, you update the UI with fadeInNewImage(_:) which performs a fade-in transition of the new googly eyes image.

Here’s a quick guide of how and when to use the various queues with async:

  • Main Queue: This is a common choice to update the UI after completing work in a task on a concurrent queue. To do this, you’ll code one closure inside another. Targeting the main queue and calling async guarantees that this new task will execute sometime after the current method finishes.
  • Global Queue: This is a common choice to perform non-UI work in the background.
  • Custom Serial Queue: A good choice when you want to perform background work serially and track it. This eliminates resource contention since you know only one task at a time is executing. Note that if you need the data from a method, you must inline another closure to retrieve it or consider using sync.

Delaying Task Execution

DispatchQueue allows you to delay task execution. Care should be taken not to use this to solve race conditions or other timing bugs through hacks like introducing delays. Use this when you want a task to run at a specific time.

let delayInSeconds = 1.0 // 1
DispatchQueue.main.asyncAfter(deadline: .now() + delayInSeconds) { // 2
  let count = PhotoManager.sharedManager.photos.count
  if count > 0 {
    self.navigationItem.prompt = nil
  } else {
    self.navigationItem.prompt = "Add photos with faces to Googlyify them!"
  }
}

Here’s what’s going on above:

  1. You specify a variable for the amount of time to delay.
  2. You then wait for the specified time then asynchronously run the block which updates the photos count and updates the prompt.

Handling the Readers-Writers Problem

GCD provides an elegant solution of creating a read/write lock using dispatch barriers. Dispatch barriers are a group of functions acting as a serial-style bottleneck when working with concurrent queues.

When you submit a DispatchWorkItem to a dispatch queue you can set flags to indicate that it should be the only item executed on the specified queue for that particular time. This means that all items submitted to the queue prior to the dispatch barrier must complete before the DispatchWorkItem will execute.

When the DispatchWorkItem‘s turn arrives, the barrier executes it and ensures that the queue does not execute any other tasks during that time. Once finished, the queue returns to its default implementation.

The diagram below illustrates the effect of a barrier on various asynchronous tasks:Dispatch-Barrier-Swift

 

Notice how in normal operation the queue acts just like a normal concurrent queue. But when the barrier is executing, it essentially acts like a serial queue. That is, the barrier is the only thing executing. After the barrier finishes, the queue goes back to being a normal concurrent queue.

Use caution when using barriers in global background concurrent queues as these queues are shared resources. Using barriers in a custom serial queue is redundant as it already executes serially. Using barriers in custom concurrent queue is a great choice for handling thread safety in atomic of critical areas of code.

You’ll use a custom concurrent queue to handle your barrier function and separate the read and write functions. The concurrent queue will allow multiple read operations simultaneously.

fileprivate let concurrentPhotoQueue =
  DispatchQueue(
    label: "com.raywenderlich.GooglyPuff.photoQueue", // 1
    attributes: .concurrent) // 2

This initializes concurrentPhotoQueue as a concurrent queue.

  1. You set up label with a descriptive name that is helpful during debugging. Typically you’ll use the reversed DNS style naming convention.
  2. You specify a concurrent queue.

Next, replace addPhoto(_:) with the following code:

func addPhoto(_ photo: Photo) {
  concurrentPhotoQueue.async(flags: .barrier) { // 1
    self._photos.append(photo) // 2
    DispatchQueue.main.async { // 3
      self.postContentAddedNotification()
    }
  }
}

Here’s how your new write function works:

  1. You dispatch the write operation asynchronously with a barrier. When it executes, it will be the only item in your queue.
  2. You add the object to the array.
  3. Finally you post a notification that you’ve added the photo. This notification should be posted on the main thread because it will do UI work. So you dispatch another task asynchronously to the main queue to trigger the notification.

To ensure thread safety with your writes, you need to perform reads on the concurrentPhotoQueue queue. You need return data from the function call so an asynchronous dispatch won’t cut it. In this case, syncwould be an excellent candidate.

Use sync to keep track of your work with dispatch barriers, or when you need to wait for the operation to finish before you can use the data processed by the closure.

You need to be careful though. Imagine if you call sync and target the current queue you’re already running on. This will result in a deadlock situation.

Two (or sometimes more) items — in most cases, threads — are said to be deadlocked if they all get stuck waiting for each other to complete or perform another action. The first can’t finish because it’s waiting for the second to finish. But the second can’t finish because it’s waiting for the first to finish.

In your case, the sync call will wait until the closure finishes, but the closure can’t finish (it can’t even start!) until the currently executing closure is finished, which can’t! This should force you to be conscious of which queue you’re calling from — as well as which queue you’re passing in.

Here’s a quick overview of when and where to use sync:

  • Main Queue: Be VERY careful for the same reasons as above; this situation also has potential for a deadlock condition.
  • Global Queue: This is a good candidate to sync work through dispatch barriers or when waiting for a task to complete so you can perform further processing.
  • Custom Serial Queue: Be VERY careful in this situation; if you’re running in a queue and call sync targeting the same queue, you’ll definitely create a deadlock.
var photos: [Photo] {
  var photosCopy: [Photo]!
  concurrentPhotoQueue.sync { // 1
    photosCopy = self._photos // 2
  }
  return photosCopy
}

Here’s what’s going on step-by-step:

  1. Dispatch synchronously onto the concurrentPhotoQueue to perform the read.
  2. Store a copy of the photo array in photosCopy and return it.

Dispatch Groups

With dispatch groups you can group together multiple tasks and either wait for them to be completed or be notified once they are complete. Tasks can be asynchronous or synchronous and can even run on different queues.

DispatchQueue.global(qos: .userInitiated).async { // 1
  var storedError: NSError?
  let downloadGroup = DispatchGroup() // 2
  for address in [overlyAttachedGirlfriendURLString, 
                  successKidURLString,
                  lotsOfFacesURLString] {
    let url = URL(string: address)
    downloadGroup.enter() // 3
    let photo = DownloadPhoto(url: url!) {
      _, error in
      if error != nil {
        storedError = error
      }   
      downloadGroup.leave() // 4
    }   
    PhotoManager.sharedManager.addPhoto(photo)
  }   
      
  downloadGroup.wait() // 5
  DispatchQueue.main.async { // 6
    completion?(storedError)
  }   
}

Here’s what the code is doing step-by-step:

  1. Since you’re using the synchronous wait method which blocks the current thread, you use async to place the entire method into a background queue to ensure you don’t block the main thread.
  2. This creates a new dispatch group.
  3. You call enter() to manually notify the group that a task has started. You must balance out the number of enter() calls with the number of leave() calls or your app will crash.
  4. Here you notify the group that this work is done.
  5. You call wait() to block the current thread while waiting for tasks’ completion. This waits forever which is fine because the photos creation task always completes. You can use wait(timeout:) to specify a timeout and bail out on waiting after a specified time.
  6. At this point, you are guaranteed that all image tasks have either completed or timed out. You then make a call back to the main queue to run your completion closure.

Dispatch groups are a good candidate for all types of queues. You should be wary of using dispatch groups on the main queue if you’re waiting synchronously for the completion of all work since you don’t want to hold up the main thread. However, the asynchronous model is an attractive way to update the UI once several long-running tasks finish, such as network calls.

Dispatching asynchronously to another queue then blocking work using wait is clumsy.

DispatchGroup manages dispatch groups. You’ll first look at its wait method. This blocks your current thread until all the group’s enqueued tasks have been completed.

 

// 1
var storedError: NSError?
let downloadGroup = DispatchGroup()
for address in [overlyAttachedGirlfriendURLString,
                successKidURLString,
                lotsOfFacesURLString] {
  let url = URL(string: address)
  downloadGroup.enter()
  let photo = DownloadPhoto(url: url!) {
    _, error in
    if error != nil {
      storedError = error
    }   
    downloadGroup.leave()
  }   
  PhotoManager.sharedManager.addPhoto(photo)
}   
    
downloadGroup.notify(queue: DispatchQueue.main) { // 2
  completion?(storedError)
}

Here’s what’s going on:

  1. In this new implementation you don’t need to surround the method in an async call since you’re not blocking the main thread.
  2. notify(queue:work:) serves as the asynchronous completion closure. It is called when there are no more items left in the group. You also specify that you want to schedule the completion work to be run on the main queue.

This is a much cleaner way to handle this particular job as it doesn’t block any threads.

Concurrency Looping

You might notice that there’s a forloop in there that cycles through three iterations and downloads three separate images. Your job is to see if you can run this for loop concurrently to try and speed things up.

This is a job for DispatchQueue.concurrentPerform(iterations:execute:). It works similarly to a for loop in that it executes different iterations concurrently. It is sychronous and returns only when all of the work is done.

Care must be taken when figuring out the optimal number of iterations for a given amount of work. Many iterations and a small amount of work per iteration can create so much overhead that it negates any gains from making the calls concurrent. The technique known as striding helps you out here. This is where for each iteration you do multiple pieces of work.

When is it appropriate to use DispatchQueue.concurrentPerform(iterations:execute:)? You can rule out serial queues because there’s no benefit there – you may as well use a normal for loop. It’s a good choice for concurrent queues that contain looping, especially if you need to keep track of progress.

var storedError: NSError?
let downloadGroup = DispatchGroup()
let addresses = [overlyAttachedGirlfriendURLString,
                 successKidURLString,
                 lotsOfFacesURLString]
let _ = DispatchQueue.global(qos: .userInitiated)
DispatchQueue.concurrentPerform(iterations: addresses.count) {
  i in
  let index = Int(i)
  let address = addresses[index]
  let url = URL(string: address)
  downloadGroup.enter()
  let photo = DownloadPhoto(url: url!) {
    _, error in
    if error != nil {
      storedError = error
    }
    downloadGroup.leave()
  }
  PhotoManager.sharedManager.addPhoto(photo)
}
downloadGroup.notify(queue: DispatchQueue.main) {
  completion?(storedError)
}

The former for loop has been replaced with DispatchQueue.concurrentPerform(iterations:execute:) to handle concurrent looping.

Running this new code on the device will occasionally produce marginally faster results. But was all this work worth it?

Actually, it’s not worth it in this case. Here’s why:

  • You’ve probably created more overhead running the threads in parallel than just running the for loop in the first place. You should use DispatchQueue.concurrentPerform(iterations:execute:) for iterating over very large sets along with the appropriate stride length.

Cancelling Dispatch Blocks

Thus far, you haven’t seen code that allows you to cancel enqueued tasks. This is where dispatch block objects represented by DispatchWorkItem comes into focus. Be aware that you can only cancel a DispatchWorkItem before it reaches the head of a queue and starts executing.

Let’s demonstrate this by starting download tasks for several images from Le Internet then cancelling some of them.

var storedError: NSError?
let downloadGroup = DispatchGroup()
var addresses = [overlyAttachedGirlfriendURLString,
                 successKidURLString,
                 lotsOfFacesURLString]
addresses += addresses + addresses // 1
var blocks: [DispatchWorkItem] = [] // 2

for i in 0 ..< addresses.count {
  downloadGroup.enter()
  let block = DispatchWorkItem(flags: .inheritQoS) { // 3
    let index = Int(i)
    let address = addresses[index]
    let url = URL(string: address)
    let photo = DownloadPhoto(url: url!) {
      _, error in
      if error != nil {
        storedError = error
      }
      downloadGroup.leave()
    }
    PhotoManager.sharedManager.addPhoto(photo)
  }
  blocks.append(block)
  DispatchQueue.main.async(execute: block) // 4
}

for block in blocks[3 ..< blocks.count] { // 5
  let cancel = arc4random_uniform(2) // 6
  if cancel == 1 {
    block.cancel() // 7
    downloadGroup.leave() // 8
  }
}

downloadGroup.notify(queue: DispatchQueue.main) {
  completion?(storedError)
}

Here’s a step-by-step walk through the code above:

  1. You expand the addresses array to hold three copies of each image.
  2. You initialize a blocks array to hold dispatch block objects for later use.
  3. You create a new DispatchWorkItem. You pass in a flags parameter to specify that the block should inherit its Quality of Service class from the queue it is dispatched to. You then define the work to be done in a closure.
  4. You dispatch the block asynchronously to the main queue. For this example, using the main queue makes it easier to cancel select blocks since it’s a serial queue. The code that sets up the dispatch blocks is already executing on the main queue so you are guaranteed that the download blocks will execute at some later time.
  5. You skip the first three download blocks by slicing the blocks array.
  6. Here you use arc4random_uniform() to randomly pick a number between 0 and 1. It’s like a coin toss.
  7. If the random number is 1 you cancel the block. This can only cancel blocks that are still in a queue and haven’t began executing. Blocks can’t be canceled in the middle of execution.
  8. Here you remember to remove the canceled block from the dispatch group.

Other GCD Fun

Semaphores

 

let url = URL(string: urlString)
let semaphore = DispatchSemaphore(value: 0) // 1
let _ = DownloadPhoto(url: url!) {
  _, error in
  if let error = error {
    XCTFail("\(urlString) failed. \(error.localizedDescription)")
  }   
  semaphore.signal() // 2
}
let timeout = DispatchTime.now() + .seconds(defaultTimeoutLengthInSeconds)
if semaphore.wait(timeout: timeout) == .timedOut { // 3
  XCTFail("\(urlString) timed out")
} 

Here’s how the semaphore works in the code above:

  1. You create a semaphore and set its start value. This represents the number of things that can access the semaphore without needing the semaphore to be incremented (note that incrementing a semaphore is known as signaling it).
  2. You signal the semaphore in the completion closure. This increments the semaphore count and signals that the semaphore is available to other resources that want it.
  3. You wait on the semaphore, with a given timeout. This call blocks the current thread until the semaphore has been signaled. A non-zero return code from this function means that the timeout was reached. In this case, the test is failed because it is deemed that the network should not take more than 10 seconds to return — a fair point!

Dispatch Sources

Dispatch sources are a particularly interesting feature of GCD. A dispatch source can basically be used to monitor for some type of event. Events can include Unix signals, file descriptors, Mach ports, VFS Nodes, and other obscure stuff.

When setting up a dispatch source, you tell it what type of events you want to monitor and the dispatch queue on which its event handler block should be executed. You then assign an event handler to the dispatch source.

Upon creation, dispatch sources start off in a suspended state. This allows for additional configuration steps to take place, for example setting up the event handler. Once you’ve configured your dispatch source, you should resume it to start processing events.

In this tutorial, you’ll get a small taste of working with dispatch sources by using it in a rather peculiar way: to monitor when your app is put into debug mode.

#if DEBUG // 1
  var signal: DispatchSourceSignal? // 2
  private let setupSignalHandlerFor = { (_ object: AnyObject) -> Void in // 3
    let queue = DispatchQueue.main
    signal =
      DispatchSource.makeSignalSource(signal: Int32(SIGSTOP), queue: queue) // 4
    signal?.setEventHandler { // 5
      print("Hi, I am: \(object.description!)")
    }
    signal?.resume() // 6
  }
#endif

The code is a little involved, so walk through it step-by-step:

  1. You compile this code only in DEBUG mode to prevent “interested parties” from gaining a lot of insight into your app. :] DEBUG is defined by adding -D DEBUG under Project Settings -> Build Settings -> Swift Compiler – Custom Flags -> Other Swift Flags -> Debug. It should be set already in the starter project.
  2. You declare a signal variable of type DispatchSourceSignal for use in monitoring Unix signals.
  3. You create a block assigned to the setupSignalHandlerFor global variable that you’ll use for one-time setup of your dispatch source.
  4. Here you set up signal. You indicate that you’re interested in monitoring the SIGSTOP Unix signal and handling received events on the main queue — you’ll discover why shortly.
  5. If the dispatch source is successfully created, you register an event handler closure that’s invoked whenever you receive the SIGSTOP signal. Your handler prints a message that includes the class description.
  6. All sources start off in the suspended state by default. Here you tell the dispatch source to resume so it can start monitoring events.

Add the following code to viewDidLoad() just below the call to super.viewDidLoad():

#if DEBUG
  _ = setupSignalHandlerFor(self)
#endif

This code invokes the dispatch source’s initialization code.

Build and run the app. Pause the program execution and resume the app immediately by tapping the pause then play buttons in Xcode’s debugger:

grand central dispatch tutorial

Check out the console. You should see something like this:

Hi, I am: <GooglyPuff.PhotoCollectionViewController: 0x7fbf0af08a10>

You app is now debugging-aware! That’s pretty awesome, but how would you use this in real life?

You could use this to debug an object and display data whenever you resume the app. You could also give your app custom security logic to protect itself (or the user’s data) when malicious attackers attach a debugger to your application.

An interesting idea is to use this approach as a stack trace tool to find the object you want to manipulate in the debugger.

Think about that situation for a second. When you stop the debugger out of the blue, you’re almost never on the desired stack frame. Now you can stop the debugger at anytime and have code execute at your desired location. This is very useful if you want to execute code at a point in your app that’s tedious to access from the debugger. Try it out!

Put a breakpoint on the print() statement inside the setupSignalHandlerFor block that you just added.

Pause in the debugger, then start again. The app will hit the breakpoint you added. You’re now deep in the depths of your PhotoCollectionViewController method. Now you can access the instance of PhotoCollectionViewController to your heart’s content. Pretty handy!

Note: If you haven’t already noticed which threads are which in the debugger, take a look at them now. The main thread will always be the first thread followed by libdispatch, the coordinator for GCD, as the second thread. After that, the thread count and remaining threads depend on what the hardware was doing when the app hit the breakpoint.

In the debugger console, type the following:

(lldb) expr object.navigationItem.prompt = "WOOT!"

The Xcode debugger can sometimes be uncooperative. If you get the message:

error: use of unresolved identifier 'self'

Then you have to do it the hard way to work around a bug in LLDB. First take note of the address of objectin the debug area:

(lldb) po object

Then manually cast the value to the type you want:

(lldb) expr let $vc = unsafeBitCast(0x7fbf0af08a10, to: GooglyPuff.PhotoCollectionViewController.self)
(lldb) expr $vc.navigationItem.prompt = "WOOT!"

With this method, you can make updates to the UI, inquire about the properties of a class, and even execute methods — all while not having to restart the app to get into that special workflow state. Pretty neat.

 

GCD(Grand Central Dispatch) Threading for Swift 3