如何在Swift 3、Swift 4及以后版本中使用dispatch_sync、dispatch_async、dispatch_after等方法?

263

我有很多Swift 2.x(甚至1.x)项目中的代码看起来像这样:

// 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
    }
}

或者像这样的东西来延迟执行:

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

或者使用Grand Central Dispatch API的各种其他用途......

现在我在Swift 3的Xcode 8(beta)中打开我的项目后,我会遇到各种错误。其中一些错误提供修复我的代码的选项,但并不是所有的修复都能产生可工作的代码。我该怎么办?


6个回答

360
自从开始,Swift 提供了一些工具来使 ObjC 和 C 更加 Swifty,并且每个版本都添加了更多的功能。现在,在 Swift 3 中,新的 "import as member" 功能允许某些风格的 C API 框架(其中你有一个类似于类的数据类型,以及一堆全局函数来处理它),更像是 Swift 原生的 API。这些数据类型作为 Swift 类导入,它们相关的全局函数作为这些类的方法和属性导入,一些相关的东西,如常量集合,在适当的情况下可以成为子类型。
在 Xcode 8 / Swift 3 beta 中,苹果应用了这个功能(以及其他一些功能)来使 Dispatch 框架更加 Swifty。(还有 Core Graphics。)如果你一直在关注 Swift 开源努力,this isn't news,但现在它是 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
    }
}

请注意,我们使用的是.userInitiated而不是旧的DISPATCH_QUEUE_PRIORITY常量之一。 在OS X 10.10 / iOS 8.0中引入了服务质量(QoS)指定符,为系统优先处理工作提供了更清晰的方式,并停用了旧的优先级指定符。 有关详细信息,请参见苹果公司关于后台工作和能源效率的文档
顺便说一下,如果您正在保留自己的队列以组织工作,现在获取一个队列的方法看起来像这样(请注意,DispatchQueueAttributes是一个OptionSet,因此您可以使用集合样式的文字组合选项):
class Foo { 
    let queue = DispatchQueue(label: "com.example.my-serial-queue",
                           attributes: [.serial, .qosUtility])
    func doStuff() {
        queue.async {
            print("Hello World")
        }
    }
}

使用dispatch_after稍后执行任务?这也是队列的一种方法,它需要一个DispatchTime,它具有各种数字类型的运算符,因此您可以添加整数或小数秒:

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

你可以在Xcode 8中打开Dispatch API的界面来了解它的使用方法 - 使用Open Quickly查找Dispatch模块,或在你的Swift项目/游乐场中放置一个符号(如DispatchQueue),然后按住Command键点击它,从那里浏览模块。(你可以在苹果漂亮的新API参考网站和Xcode文档查看器中找到Swift Dispatch API,但似乎C版本的文档内容还没有移入其中。)
更多提示请参阅迁移指南

3
关于Xcode 8 Beta 6,.serial属性已经被删除,并且默认行为已更改。详见https://forums.developer.apple.com/message/159457#159457。 - hyouuu
7
这需要更新,因为XCode 8.1中属性标签已经消失了,我们可以使用“DispatchQueue.global(qos: .background).async”代替。 - Mike M
2
很棒的回答。确实帮助我理解了它。 - Mohsin Khubaib Ahmed
在Swift中是否有一种方法可以拥有DispatchQueue相同的行为,而不需要针对iOS 10进行目标设置? - superpuccio
如果我没记错的话,所有Swift 3 Dispatch API都在[overlay](https://github.com/apple/swift/tree/master/stdlib/public/SDK/Dispatch)中-也就是说,它是Swift 3语言/编译器/标准库的一部分,可以重新映射或调用相同的底层C API,因此无论您瞄准哪个操作系统,它都可用。(也就是说,假设您要进行的特定调用在目标操作系统上可用-请注意,一些Dispatch API具有可用性注释。) - rickster
显示剩余3条评论

151
在Xcode 8测试版4中不起作用...
使用:
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
    print("Are we there yet?")
}

对于异步的双向通信:

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

DispatchQueue.main.async( execute: {
    print("Async2")
})

那么它不会阻塞用户界面吗? - user25
是的,它不会阻塞。 - ingconti

74

以下是关于 async 的一个很好的 Swift 4 实例:


DispatchQueue.global(qos: .background).async {
    // Background Thread
    DispatchQueue.main.async {
        // Run UI Updates or call completion block
    }
}

嗨,DispatchQueue.main.async { // 执行UI更新 } 在后台线程之前执行 - Uma Achanta
类似于 Kotlin 的协程 - user25

41

在 Xcode 8 中使用:

DispatchQueue.global(qos: .userInitiated).async { }

31

Swift 5.2、4及更高版本

主队列和后台队列

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

使用异步和同步线程!

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

异步线程将与主线程一起工作。

同步线程在执行时会阻塞主线程。


1
你如何在不阻塞主线程(UI)的情况下使用同步线程?我想在后台执行一系列任务,但这些任务必须以同步方式一个接一个地执行。在此期间,UI 应保持响应性... 你会怎么做? - iKK
使用NSOperationQueue。其中每个任务表示为一个NSOperation。请参见https://dev59.com/WmIk5IYBdhLWcg3wIq5U#19746890 - Saranjith
救了我的一天!谢谢! - Codetard

16

Swift 4.1和5.我们在代码中的许多地方都使用队列。因此,我创建了Threads类来管理所有队列。如果您不想使用Threads类,可以从类方法中复制所需的队列代码。

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()
    }
  }
}

演示使用主队列的示例。

override func viewDidLoad() {
    super.viewDidLoad()
     Threads.performTaskInMainQueue {
        //Update UI
    }
}

太棒了,谢谢! - Menio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接