Grand Central Dispatch Tutorial for Swift: Part 2/2

Grand Central Dispatch Tutorial for Swift: Part 2/2
Learn about concurrency in this Grand Central Dispatch in-depth tutorial series.

Learn about concurrency in this Grand Central Dispatch in-depth tutorial series.

Update note: This tutorial was updated for iOS 8 and Swift by Bjørn Ruud. Original post by Tutorial Team member Derek Selander.

Welcome to the second and final part of this Grand Central Dispatch tutorial series!

In the first part of this series, you learned about concurrency, threading, and how GCD works. You made the PhotoManager singleton thread safe for reading and writing of photos using a combination of dispatch_barrier_async and dispatch_sync. In addition to all that, you enhanced the UX of the app through the timing of a prompt with dispatch_after, and offloaded the work from the instantiation of a view controller to perform a CPU intensive task with dispatch_async.

If you have been following along, you can pick up where you left off with the sample project form Part 1. If you haven’t completed Part 1 or don’t want to reuse your project you can download the finished project from the first part of this tutorial here.

It’s time to explore some more GCD!

Correcting the Premature Popup

You may have noticed that when you try to add photos with the Le Internet option, an alert view pops up well before the images have finished downloading, as shown in the screenshot below:

Premature Completion Block

The fault lies with PhotoManager’s downloadPhotosWithCompletion which has been reproduced below:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
  var storedError: NSError!
  for address in [OverlyAttachedGirlfriendURLString,
                  SuccessKidURLString,
                  LotsOfFacesURLString] {
    let url = NSURL(string: address)
    let photo = DownloadPhoto(url: url!) {
      image, error in
      if error != nil {
        storedError = error
      }
    }
    PhotoManager.sharedManager.addPhoto(photo)
  }
 
  if let completion = completion {
    completion(error: storedError)
  }
}

Here you call the completion closure at the end of the method — you’re assuming all of the photo downloads have completed. But unfortunately, there’s no guarantee that all the downloads have finished by this point.

The DownloadPhoto class’s instantiation method starts downloading a file from a URL and returns immediately before the download completes. In other words, downloadPhotosWithCompletion calls its own completion closure at the end, as if its own method body were all straight-line synchronous code and every method call that completed had finished its work.

However, DownloadPhoto(url:) is asynchronous and returns immediately — so this approach won’t work.

Instead, downloadPhotosWithCompletion should call its own completion closure only after all the image download tasks have called their own completion closures. The question is: how do you monitor concurrent asynchronous events? You don’t know when they will complete, and they can finish in any order.

Perhaps you could write some hacky code that uses multiple Bool values to keep track of each download, but that doesn’t scale well, and frankly, it makes for pretty ugly code.

Fortunately, this type of multiple asynchronous completion monitoring is exactly what dispatch groups were designed for.

Dispatch Groups

Dispatch groups notify you when an entire group of tasks completes. These tasks can be either asynchronous or synchronous and can even be tracked from different queues. Dispatch groups also notify you in synchronous or asynchronous fashion when all of the group’s events are complete. Since items are being tracked on different queues, an instance of dispatch_group_t keeps track of the different tasks in the queues.

The GCD API provides two ways to be notified when all events in the group have completed.

The first one, dispatch_group_wait, is a function that blocks your current thread and waits until either all the tasks in the group have completed, or until a timeout occurs. This is exactly what you want in this case.

Open PhotoManager.swift and replace downloadPhotosWithCompletion with the following implementation:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
  dispatch_async(GlobalUserInitiatedQueue) { // 1
    var storedError: NSError!
    var downloadGroup = dispatch_group_create() // 2
 
    for address in [OverlyAttachedGirlfriendURLString,
                    SuccessKidURLString,
                    LotsOfFacesURLString]
    {
      let url = NSURL(string: address)
      dispatch_group_enter(downloadGroup) // 3
      let photo = DownloadPhoto(url: url!) {
        image, error in
        if let error = error {
          storedError = error
        }
        dispatch_group_leave(downloadGroup) // 4
      }
      PhotoManager.sharedManager.addPhoto(photo)
    }
 
    dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER) // 5
    dispatch_async(GlobalMainQueue) { // 6
      if let completion = completion { // 7
        completion(error: storedError)
      }
    }
  }
}

Taking each numbered comment in turn, you’ll see the following:

  1. Since you’re using the synchronous dispatch_group_wait which blocks the current thread, you use dispatch_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 which behaves somewhat like a counter of the number of uncompleted tasks.
  3. dispatch_group_enter manually notifies a group that a task has started. You must balance out the number of dispatch_group_enter calls with the number of dispatch_group_leave calls or else your app will crash.
  4. Here you manually notify the group that this work is done. Again, you’re balancing all group enters with an equal amount of group leaves.
  5. dispatch_group_wait waits until either all of the tasks are complete or until the time expires. If the time expires before all events complete, the function will return a non-zero result. You could put this into a conditional closure to check if the waiting period expired; however, in this case you specified for it to wait forever by supplying DISPATCH_TIME_FOREVER. This means, unsurprisingly, it’ll wait forever! That’s fine, because the completion of the photos creation will always complete.
  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. This will append work onto the main thread to be executed at some later time.
  7. Finally, run the completion closure if one was supplied.

Build and run your app, attempt to download multiple images and notice how your app behaves with the completion closure in place.

Note: If the network activities occur too quickly to discern when the completion closure should be called and you’re running the app on a device, you can make sure this really works by toggling some network settings in the Developer Section of the Settings app. Just go to the Network Link Conditioner section, enable it, and select a profile. “Very Bad Network” is a good choice.

If you are running on the Simulator, you can use the Network Link Conditioner included in the Hardware IO Tools for Xcode to change your network speed. This is a good tool to have in your arsenal because it forces you to be conscious of what happens to your apps when connection speeds are less than optimal.

This solution is good so far, but in general it’s best to avoid blocking threads if at all possible. Your next task is to rewrite the same method to notify you asynchronously when all the downloads have completed.

Before we head on to another use of dispatch groups, here’s a brief guide on when and how to use dispatch groups with the various queue types:

  • Custom Serial Queue: This is a good candidate for notifications when a group of tasks completes.
  • Main Queue (Serial): This is a good candidate as well in this scenario. You should be wary of using this on the main queue if you are 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.
  • Concurrent Queue: This as well is a good candidate for dispatch groups and completion notifications.

Dispatch Groups, Take Two

That’s all well and good, but it’s a bit clumsy to have to dispatch asynchronously onto another queue and then block using dispatch_group_wait. There’s another way…

Find downloadPhotosWithCompletion in PhotoManager.swift and replace it with this implementation:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
  // 1
  var storedError: NSError!
  var downloadGroup = dispatch_group_create()
 
  for address in [OverlyAttachedGirlfriendURLString,
                  SuccessKidURLString,
                  LotsOfFacesURLString]
  {
    let url = NSURL(string: address)
    dispatch_group_enter(downloadGroup)
    let photo = DownloadPhoto(url: url!) {
      image, error in
      if let error = error {
        storedError = error
      }
      dispatch_group_leave(downloadGroup)
    }
    PhotoManager.sharedManager.addPhoto(photo)
  }
 
  dispatch_group_notify(downloadGroup, GlobalMainQueue) { // 2
    if let completion = completion {
      completion(error: storedError)
    }
  }
}

Here’s how your new asynchronous method works:

  1. In this new implementation you don’t need to surround the method in a dispatch_async call since you’re not blocking the main thread.
  2. dispatch_group_notify serves as the asynchronous completion closure. This code executes when there are no more items left in the dispatch group and it’s the completion closure’s turn to run. You also specify on which queue to run your completion code. Here, the main queue is the one you want.

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

The Perils of Too Much Concurrency

With all of these new tools at your disposal, you should probably thread everything, right!?

Thread_All_The_Code_Meme

Take a look at downloadPhotosWithCompletion in PhotoManager. You might notice that there’s a for loop 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 it up.

This is a job for dispatch_apply.

dispatch_apply acts like a for loop which executes different iterations concurrently. This function is sychronous, so just like a normal for loop, dispatch_apply returns only when all of the work is done.

Care must be taken when figuring out the optimal amount of iterations for any given amount of work inside the closure, since 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 dispatch_apply?

  • Custom Serial Queue: A serial queue would completely negate the use of dispatch_apply; you might as well just use a normal for loop.
  • Main Queue (Serial): Just as above, using this on a serial queue is a bad idea. Just use a normal for loop.
  • Concurrent Queue: This is a good choice for concurrent looping, especially if you need to track the progress of your tasks.

Head back to downloadPhotosWithCompletion and replace it with the following implementation:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
  var storedError: NSError!
  var downloadGroup = dispatch_group_create()
  let addresses = [OverlyAttachedGirlfriendURLString,
                   SuccessKidURLString,
                   LotsOfFacesURLString]
 
  dispatch_apply(UInt(addresses.count), GlobalUserInitiatedQueue) {
    i in
    let index = Int(i)
    let address = addresses[index]
    let url = NSURL(string: address)
    dispatch_group_enter(downloadGroup)
    let photo = DownloadPhoto(url: url!) {
      image, error in
      if let error = error {
        storedError = error
      }
      dispatch_group_leave(downloadGroup)
    }
    PhotoManager.sharedManager.addPhoto(photo)
  }
 
  dispatch_group_notify(downloadGroup, GlobalMainQueue) {
    if let completion = completion {
      completion(error: storedError)
    }
  }
}

Your loop is now running concurrently; in the code above, in the call to dispatch_apply, you supply the amount of iterations with the first parameter, the queue to perform the tasks on in the second parameter and the closure action in the third parameter.

Be aware that although you have code that will add the photos in a thread safe manner, the ordering of the images could be different depending on which thread finishes first.

Build and run, then add some photos from Le Internet. Notice anything different?

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 dispatch_apply for iterating over very large sets along with the appropriate stride length.
  • Your time to create an app is limited — don’t waste time pre-optimizing code that you don’t know is broken. If you’re going to optimize something, optimize something that is noticeable and worth your time. Find the methods with the longest execution times by profiling your app in Instruments. Check out How to Use Instruments in Xcode to learn more.
  • Typically, optimizing code makes your code more complicated for yourself and for other developers coming after you. Make sure the added complication is worth the benefit.

Remember, don’t go crazy with optimizations. You’ll only make it harder on yourself and others who have to wade through your code.

Cancelling Dispatch Blocks

New in iOS 8 and OS X Yosemite is the introduction of dispatch block objects. These are implemented as a wrapper around ordinary closures and behave just like them. Dispatch block objects can do a number of things, like set a Quality of Service class per object for internal prioritization in a queue, but most notably is the ability to cancel the execution of block objects. Be aware that a block object can only be cancelled before it reaches the head of a queue and start executing.

Let’s demonstrate this by starting download tasks for several copies of images from Le Internet, and then cancelling some of them. Select PhotoManager.swift and replace downloadPhotosWithCompletion with the following:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) {
  var storedError: NSError!
  let downloadGroup = dispatch_group_create()
  var addresses = [OverlyAttachedGirlfriendURLString,
                   SuccessKidURLString,
                   LotsOfFacesURLString]
  addresses += addresses + addresses // 1
  var blocks: [dispatch_block_t] = [] // 2
 
  for i in 0 ..< addresses.count {
    dispatch_group_enter(downloadGroup)
    let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) { // 3
      let index = Int(i)
      let address = addresses[index]
      let url = NSURL(string: address)
      let photo = DownloadPhoto(url: url!) {
        image, error in
        if let error = error {
          storedError = error
        }
        dispatch_group_leave(downloadGroup)
      }
      PhotoManager.sharedManager.addPhoto(photo)
    }
    blocks.append(block)
    dispatch_async(GlobalMainQueue, block) // 4
  }
 
  for block in blocks[3 ..< blocks.count] { // 5
    let cancel = arc4random_uniform(2) // 6
    if cancel == 1 {
      dispatch_block_cancel(block) // 7
      dispatch_group_leave(downloadGroup) // 8
    }
  }
 
  dispatch_group_notify(downloadGroup, GlobalMainQueue) {
    if let completion = completion {
      completion(error: storedError)
    }
  }
}
  1. The addresses array is expanded to hold three of each address.
  2. This array will hold the created block objects for later use.
  3. dispatch_block_create creates a new block object. The first parameter is a flag defining various block traits. The flag used here makes the block inherit its QoS class from the queue it is dispatched to. The second parameter is the block definition in the form of a closure.
  4. Here the block is dispatched asynchronously to the global 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. The first three downloads are left alone, and the array is sliced to get the rest.
  6. arc4random_uniform provides an integer between 0 and an upper bound (not inclusive). By using 2 for the upper bound you get either a 0 or a 1, like a coin toss.
  7. If the random number is 1 the block is cancelled. That is, if the block is still in a queue and has not begun executing. Blocks can not be cancelled in the middle of execution.
  8. Since all blocks are added to the dispatch group, remember to remove the cancelled ones.

Build and run, and add images from Le Internet. You’ll see that the app now downloads three of each image and a random extra amount. The rest have been cancelled after being dispatched to a queue. This is a pretty contrived example but it illustrates how dispatch block objects are used and cancelled nicely.

Dispatch block objects can do a lot more, so be sure to check out the documentation.

Miscellaneous GCD Fun

But wait! There’s more! Here are some extra functions that are a little farther off the beaten path. Although you won’t use these tools nearly as frequently, they can be tremendously helpful in the right situations.

Testing Asynchronous Code

This might sound like a crazy idea, but did you know that Xcode has testing functionality? :] I know, sometimes I like to pretend it’s not there, but writing and running tests is important when building complex relationships in code.

Testing in Xcode is performed on subclasses of XCTestCase and runs any method in its method signature that begins with test. Testing is measured on the main thread, so you can assume that every test happens in a serial manner.

As soon as a given test method completes, XCTest methods will consider a test to be finished and move onto the next test. That means that any asynchronous code from the previous test will continue to run while the next test is running.

Networking code is usually asynchronous, since you don’t want to block the main thread while performing a network fetch. That, coupled with the fact that tests finish when the test method finishes, can make it hard to test networking code.

Let’s take a brief look at two common techniques for testing asynchronous code: one using semaphores and one using expectations.

Semaphores

Semaphores are an old-school threading concept introduced to the world by the ever-so-humble Edsger W. Dijkstra. Semaphores are a complex topic because they build upon the intricacies of operating system functions.

If you want to learn more about semaphores, check out this link which discusses semaphore theory in more detail. If you’re the academic type, a classic software development problem that uses semaphores is the Dining Philosophers Problem.

Semaphores lets you control the access of multiple consumers into a finite amount of resources. For example, if you created a semaphore with a pool of two resources, at most only two threads could access the critical section at the same time. Other items that want to use the resource must wait in a FIFO queue.

Open GooglyPuffTests.swift and replace downloadImageURLWithString with the following implementation:

func downloadImageURLWithString(urlString: String) {
  let url = NSURL(string: urlString)
  let semaphore = dispatch_semaphore_create(0) // 1
  let photo = DownloadPhoto(url: url!) {
    image, error in
    if let error = error {
      XCTFail("\(urlString) failed. \(error.localizedDescription)")
    }
    dispatch_semaphore_signal(semaphore) // 2
  }
 
  let timeout = dispatch_time(DISPATCH_TIME_NOW, DefaultTimeoutLengthInNanoSeconds)
  if dispatch_semaphore_wait(semaphore, timeout) != 0 { // 3
    XCTFail("\(urlString) timed out")
  }
}

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

  1. Create the semaphore. The parameter indicates the value the semaphore starts with. This number is the number of things that can access the semaphore without having to have something increment it first (note that incrementing a semaphore is known as signalling it).
  2. In the completion closure you tell the semaphore that you no longer need the resource. This increments the semaphore count and signals that the semaphore is available to other resources that want it.
  3. This waits on the semaphore, with a given timeout. This call blocks the current thread until the semaphore has been signalled. 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!

Run your tests by selecting Product / Test from the menu or use ⌘+U if you have the default key bindings. They should all succeed in a timely manner.

Disable your connection and run the tests again; if you are running on a device, put it in airplane mode. If you’re running on the simulator then simply turn off your connection. The tests complete with a fail result, after 10 seconds. Great, it worked!

These are rather trivial tests, but if you are working with a server team then these basic tests can prevent a wholesome round of finger-pointing of who is to blame for the latest network issue.

Expectations

The XCTest framework provides another solution to the asynchronous code testing problem in the form of expectations. This feature lets you set up an expectation – something you expect will happen – and then start an asynchronous task. Then you can have the test runner wait until the asynchronous task marks the expectation as fulfilled.

Navigate to GooglyPuffTests.swift and replace downloadImageURLWithString with the following code:

func downloadImageURLWithString(urlString: String) {
  let url = NSURL(string: urlString)
  let downloadExpectation = expectationWithDescription("Image downloaded from \(urlString)") // 1
  let photo = DownloadPhoto(url: url!) {
    image, error in
    if let error = error {
      XCTFail("\(urlString) failed. \(error.localizedDescription)")
    }
    downloadExpectation.fulfill() // 2
  }
 
  waitForExpectationsWithTimeout(10) { // 3
    error in
    if let error = error {
      XCTFail(error.localizedDescription)
    }
  }
}

Here is how it works:

  1. Create the expectation with expectationWithDescription. The test runner will display the string parameter here in the test log upon failure, so describe what you expect to happen.
  2. Call fulfill in the closure that executes asynchronously to mark it as fulfilled.
  3. The calling thread waits for expectations to be fulfilled by calling waitForExpectationsWithTimeout. If the wait times out, that’s treated as an error.

Build and run the tests. The end result is not very different from using a semaphore, but leveraging the XCTest framework is a cleaner and more readable solution.

Working With Dispatch Sources

A particularly interesting feature of GCD is Dispatch Sources, which are basically a grab-bag of low-level functionality helping you to respond to or monitor Unix signals, file descriptors, Mach ports, VFS Nodes, and other obscure stuff. All of this is far beyond the scope of this tutorial, but you’ll get a small taste of it by implementing a dispatch source object and using it in a rather peculiar way.

First-time users of dispatch sources can get quite lost on how to use a source, so the first thing you need to understand how dispatch_source_create works. This is the function prototype for creating a source:

func dispatch_source_create(
  type: dispatch_source_type_t,
  handle: UInt,
  mask: UInt,
  queue: dispatch_queue_t!) -> dispatch_source_t!

The first parameter, type: dispatch_source_type_t, is the most important parameter as it dictates what the handle and mask parameters will be. You’ll need to refer to the Xcode documentation to see what options are available for each dispatch_source_type_t parameter.

Here you’ll be monitoring for DISPATCH_SOURCE_TYPE_SIGNAL. As the documentation says:

A dispatch source that monitors the current process for signals. The handle is a signal number (int). The mask is unused (pass zero for now).

A list of these Unix signals can found in the header file signal.h. At the top there are a bunch of #defines. From that list of signals, you will be monitoring the SIGSTOP signal. This signal is sent when a process receives an unavoidable suspend instruction. This is the same signal that’s sent when you debug your application using the LLDB debugger.

Go to PhotoCollectionViewController.swift and add the following code near viewDidLoad. You’ll want to add these two private properties to the class, and this new block of code at the beginning of viewDidLoad, after the call to the superclass but before the existing line that gets the ALAssetLibrary:

#if DEBUG
private var signalSource: dispatch_source_t!
private var signalOnceToken = dispatch_once_t()
#endif
 
override func viewDidLoad() {
  super.viewDidLoad()
 
  #if DEBUG // 1
  dispatch_once(&signalOnceToken) { // 2
    let queue = dispatch_get_main_queue()
    self.signalSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL,
                                               UInt(SIGSTOP), 0, queue) // 3
    if let source = self.signalSource { // 4
      dispatch_source_set_event_handler(source) { // 5
        NSLog("Hi, I am: \(self.description)")
      }
      dispatch_resume(source) // 6
    }
  }
  #endif
 
  // The other stuff
}

The code is a little involved, so step through the code one comment at a time to see what’s going on:

  1. It’s best to only compile this code while in DEBUG mode since this could give “interested parties” 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.
  2. Use dispatch_once to perform the dispatch source’s one-time setup.
  3. Here you instantiate the signalSource variable. You indicate that you’re interested in signal monitoring and provide the SIGSTOP signal as the second parameter. Additionally, you use the main queue for handling received events — you’ll discover why shortly.
  4. A dispatch source object won’t be created if you provide malformed parameters. As a result, you should make sure you have a valid dispatch source object before working on it.
  5. dispatch_source_set_event_handler registers an event handler closure that is invoked when you receive the signal you’re monitoring for.
  6. By default, all sources start in the suspended state. You must tell the source object to resume when you want to start monitoring for the events.

Build and run your app; pause in the debugger and resume the app immediately. Check out the console, and you’ll see something like this in the debugger:

2014-08-12 12:24:00.514 GooglyPuff[24985:5481978] Hi, I am: <GooglyPuff.PhotoCollectionViewController: 0x7b765ee0>

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.

What_Meme

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 NSLog statement in viewDidLoad in the event handler you just added. Pause in the debugger, then start again; the app will then 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, type the following:

po self.navigationItem.prompt = "WOOT!"

Then resume execution of the app. You’ll see the following:

GooglyPuff_Swift_signal_break
GooglyPuff_Swift_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.

Where to Go From Here?

You can download the final project here.

I hate to bang on this subject again, but you really should check out the How to Use Instruments tutorial. You’ll definitely need this if you plan on doing any optimization of your apps. Be aware that Instruments is good for profiling relative execution: comparing which areas of code takes longer in relation to other areas. If you’re trying to figure out the actual execution time of a method, you might need to come up with a more home-brewed solution.

Also check out How to Use NSOperations and NSOperationQueue Tutorial in Swift, a concurrency technology that is built on top of GCD. In general, it’s best practice to use GCD if you are using simple fire-and-forget tasks. NSOperations offers better control, an implementation for handling maximum concurrent operations, and a more object-oriented paradigm at the cost of speed.

Remember, unless you have a specific reason to go lower, always try and stick with a higher level API. Only venture into the dark arts of Apple if you want to learn more or to do something really, really “interesting”. :]

Good luck and have fun! Post any questions or feedback in the discussion below!

Grand Central Dispatch Tutorial for Swift: Part 2/2 is a post from: Ray Wenderlich

The post Grand Central Dispatch Tutorial for Swift: Part 2/2 appeared first on Ray Wenderlich.

9
Like
Save

Comments

Write a comment

*