How do I dispatch_sync, dispatch_async, dispatch_after, etc in Swift 3, Swift 4, and beyond?
Asked Answered
V

6

263

I have lots of code in Swift 2.x (or even 1.x) projects that looks like this:

// Move to a background thread to do some long running work
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
    let image = self.loadOrGenerateAnImage()
    // Bounce back to the main thread to update the UI
    dispatch_async(dispatch_get_main_queue()) {
        self.imageView.image = image
    }
}

Or stuff like this to delay execution:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(0.5 * Double(NSEC_PER_SEC))), dispatch_get_main_queue()) {
    print("test")
}

Or any of all kinds of other uses of the Grand Central Dispatch API...

Now that I've opened my project in Xcode 8 (beta) for Swift 3, I get all kinds of errors. Some of them offer to fix my code, but not all of the fixes produce working code. What do I do about this?

Verdieverdigris answered 14/6, 2016 at 0:58 Comment(1)
Answered here: #37806385Torietorii
V
361

Since the beginning, Swift has provided some facilities for making ObjC and C more Swifty, adding more with each version. Now, in Swift 3, the new "import as member" feature lets frameworks with certain styles of C API -- where you have a data type that works sort of like a class, and a bunch of global functions to work with it -- act more like Swift-native APIs. The data types import as Swift classes, their related global functions import as methods and properties on those classes, and some related things like sets of constants can become subtypes where appropriate.

In Xcode 8 / Swift 3 beta, Apple has applied this feature (along with a few others) to make the Dispatch framework much more Swifty. (And Core Graphics, too.) If you've been following the Swift open-source efforts, this isn't news, but now is the first time it's part of Xcode.

Your first step on moving any project to Swift 3 should be to open it in Xcode 8 and choose Edit > Convert > To Current Swift Syntax... in the menu. This will apply (with your review and approval) all of the changes at once needed for all the renamed APIs and other changes. (Often, a line of code is affected by more than one of these changes at once, so responding to error fix-its individually might not handle everything right.)

The result is that the common pattern for bouncing work to the background and back now looks like this:

// Move to a background thread to do some long running work
DispatchQueue.global(qos: .userInitiated).async {
    let image = self.loadOrGenerateAnImage()
    // Bounce back to the main thread to update the UI
    DispatchQueue.main.async {
        self.imageView.image = image
    }
}

Note we're using .userInitiated instead of one of the old DISPATCH_QUEUE_PRIORITY constants. Quality of Service (QoS) specifiers were introduced in OS X 10.10 / iOS 8.0, providing a clearer way for the system to prioritize work and deprecating the old priority specifiers. See Apple's docs on background work and energy efficiency for details.

By the way, if you're keeping your own queues to organize work, the way to get one now looks like this (notice that DispatchQueueAttributes is an OptionSet, so you use collection-style literals to combine options):

class Foo { 
    let queue = DispatchQueue(label: "com.example.my-serial-queue",
                           attributes: [.serial, .qosUtility])
    func doStuff() {
        queue.async {
            print("Hello World")
        }
    }
}

Using dispatch_after to do work later? That's a method on queues, too, and it takes a DispatchTime, which has operators for various numeric types so you can just add whole or fractional seconds:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // in half a second...
    print("Are we there yet?")
}

You can find your way around the new Dispatch API by opening its interface in Xcode 8 -- use Open Quickly to find the Dispatch module, or put a symbol (like DispatchQueue) in your Swift project/playground and command-click it, then brouse around the module from there. (You can find the Swift Dispatch API in Apple's spiffy new API Reference website and in-Xcode doc viewer, but it looks like the doc content from the C version hasn't moved into it just yet.)

See the Migration Guide for more tips.

Verdieverdigris answered 14/6, 2016 at 0:58 Comment(8)
As for Xcode 8 Beta 6, the .serial attribute is gone and the default behavior - forums.developer.apple.com/message/159457#159457Moriarty
This needs an update since XCode 8.1.. the attributes label has disappeared and in its place we can use 'DispatchQueue.global(qos: .background).async'Radiative
Wonderful answer. Really helped me get my head around it.Zugzwang
I had to use qos: instead of attributes:Tautonym
Shouldn't that be myQueue.async { in the class Foo example?Tumid
Is there a way in Swift to have the same behaviour of DispatchQueue without the need to target iOS 10?Refection
@Refection IIRC all the Swift 3 Dispatch API is in the overlay — that is, it's part of the Swift 3 language / compiler / standard library and either remaps or calls through to the same underlying C API, so it's available no matter which OS you target. (That is, assuming the specific calls you're making are available on your target OS — notice some of the Dispatch API have availability annotations.)Verdieverdigris
hi I've tried the first one but DispatchQueue.main.async {} is executing prior ti the background taskCohlier
T
151

In Xcode 8 beta 4 does not work...

Use:

DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    print("Are we there yet?")
}

for async two ways:

DispatchQueue.main.async {
    print("Async1")
}

DispatchQueue.main.async( execute: {
    print("Async2")
})
Threepence answered 13/8, 2016 at 8:54 Comment(2)
So it doesn't block UI?Hydrated
yes, it doesn't to block.Threepence
D
74

This one is good example for Swift 4 about async:

DispatchQueue.global(qos: .background).async {
    // Background Thread
    DispatchQueue.main.async {
        // Run UI Updates or call completion block
    }
}
Diaphragm answered 1/3, 2017 at 12:2 Comment(2)
hi DispatchQueue.main.async { // Run UI Updates } is executing prior to background threadCohlier
similar with Kotlin's coroutinesHydrated
U
41

in Xcode 8 use:

DispatchQueue.global(qos: .userInitiated).async { }
Unreasoning answered 9/10, 2016 at 16:7 Comment(0)
T
31

Swift 5.2, 4 and later

Main and Background Queues

let main = DispatchQueue.main
let background = DispatchQueue.global()
let helper = DispatchQueue(label: "another_thread") 

Working with async and sync threads!

 background.async { //async tasks here } 
 background.sync { //sync tasks here } 

Async threads will work along with the main thread.

Sync threads will block the main thread while executing.

Tax answered 17/10, 2017 at 11:41 Comment(3)
And how would you use Sync threads without blocking the main thread (UI) ?? I would like to execute a row of things in the background - but theses things must be executed one after the other in a sync way. During this time the UI should stay responsive.... How would you do that ?Chavarria
Use NSOperationQueue. Which your each task representing a NSOperation. refer https://mcmap.net/q/24010/-nsoperation-and-nsoperationqueue-working-thread-vs-main-threadTax
Saved my day! Thank you!Pinkerton
E
16

Swift 4.1 and 5. We use queues in many places in our code. So, I created Threads class with all queues. If you don't want to use Threads class you can copy the desired queue code from class methods.

class Threads {

  static let concurrentQueue = DispatchQueue(label: "AppNameConcurrentQueue", attributes: .concurrent)
  static let serialQueue = DispatchQueue(label: "AppNameSerialQueue")

  // Main Queue
  class func performTaskInMainQueue(task: @escaping ()->()) {
    DispatchQueue.main.async {
      task()
    }
  }

  // Background Queue
  class func performTaskInBackground(task:@escaping () throws -> ()) {
    DispatchQueue.global(qos: .background).async {
      do {
        try task()
      } catch let error as NSError {
        print("error in background thread:\(error.localizedDescription)")
      }
    }
  }

  // Concurrent Queue
  class func perfromTaskInConcurrentQueue(task:@escaping () throws -> ()) {
    concurrentQueue.async {
      do {
        try task()
      } catch let error as NSError {
        print("error in Concurrent Queue:\(error.localizedDescription)")
      }
    }
  }

  // Serial Queue
  class func perfromTaskInSerialQueue(task:@escaping () throws -> ()) {
    serialQueue.async {
      do {
        try task()
      } catch let error as NSError {
        print("error in Serial Queue:\(error.localizedDescription)")
      }
    }
  }

  // Perform task afterDelay
  class func performTaskAfterDealy(_ timeInteval: TimeInterval, _ task:@escaping () -> ()) {
    DispatchQueue.main.asyncAfter(deadline: (.now() + timeInteval)) {
      task()
    }
  }
}

Example showing the use of main queue.

override func viewDidLoad() {
    super.viewDidLoad()
     Threads.performTaskInMainQueue {
        //Update UI
    }
}
Encratis answered 13/9, 2018 at 17:59 Comment(1)
Excellent, Thanks!Indemnify

© 2022 - 2024 — McMap. All rights reserved.