Swift4 - GCD的使用

从Swift3开始GCD的API就发生了很大的变化,更加简洁,使用起来更方便。像我们经常开启一个异步线程处理事情然后切回主线程刷新UI操作,这里就变的非常简单了。

    DispatchQueue.global().async {

        // do async task

        DispatchQueue.main.async {

            // update UI

          }

      }

DispatchQueue

DispatchQueue字面意思就是派发列队,主要是管理需要执行的任务,任务以闭包或者DispatchWorkItem的方式进行提交.列队中的任务遵守FIFO原则。如果对于列队不是很了解,可以看这里。 列队可以是串行也可以是并发,串行列队按顺序执行,并发列队会并发执行任务,但是我们并不知道具体任务的执行顺序。

列队的分类

系统列队

主列队

let mainQueue = DispatchQueue.main

全局列队

let globalQueue = DispatchQueue.global()

用户创建列队

创建自己的列队,简单的方式就是指定列队的名称即可

let queue = DispatchQueue(label: "com.conpanyName.queue")

这样的初始化的列队有着默认的配置项,默认的列队是串行列队。便捷构造函数如下

public convenience init(label: String, qos: DispatchQoS = default, attributes: DispatchQueue.Attributes = default, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = default, target: DispatchQueue? = default)

我们也可以自己显示设置相关属性,创建一个并发列队

    let label = "com.conpanyName.queue"

    let qos = DispatchQoS.default

    let attributes = DispatchQueue.Attributes.concurrent

    let autoreleaseFrequnecy = DispatchQueue.AutoreleaseFrequency.never

    let queue = DispatchQueue(label: label, qos: qos, attributes: attributes, autoreleaseFrequency: autoreleaseFrequnecy, target: nil)

参数介绍

label:列队的标识符,能够方便区分列队进行调试

qos:列队的优先级(quality of service),其值如下:

      public struct DispatchQoS : Equatable {

            public static let background: DispatchQoS

            public static let utility: DispatchQoS

            public static let `default`: DispatchQoS

            public static let userInitiated: DispatchQoS

            public static let userInteractive: DispatchQoS

            public static let unspecified: DispatchQoS

      }

优先级由最低的background到最高的userInteractive共五个,还有一个为定义的unspecified.

background:最低优先级,等同于DISPATCH_QUEUE_PRIORITY_BACKGROUND. 用户不可见,比如:在后台存储大量数据

utility:优先级等同于DISPATCH_QUEUE_PRIORITY_LOW,可以执行很长时间,再通知用户结果。比如:下载一个大文件,网络,计算

default:默认优先级,优先级等同于DISPATCH_QUEUE_PRIORITY_DEFAULT,建议大多数情况下使用默认优先级

userInitiated:优先级等同于DISPATCH_QUEUE_PRIORITY_HIGH,需要立刻的结果

.userInteractive:用户交互相关,为了好的用户体验,任务需要立马执行。使用该优先级用于UI更新,事件处理和小工作量任务,在主线程执行。

Qos指定了列队工作的优先级,系统会根据优先级来调度工作,越高的优先级能够越快被执行,但是也会消耗功能,所以准确的指定优先级能够保证app有效的使用资源。详细可以看这里

attributes:列队的属性,也可以说是类型,即是并发还是串行。attributes是一个结构体并遵守OptionSet协议,所以传入的参数可以为[.option1, .option2]

    public struct Attributes : OptionSet {

          public let rawValue: UInt64

          public init(rawValue: UInt64)

          public static let concurrent: DispatchQueue.Attributes

          public static let initiallyInactive: DispatchQueue.Attributes

    }

默认:列队是串行的

.concurrent:列队是并发的

.initiallyInactive:列队不会自动执行,需要开发中手动触发

autoreleaseFrequency:自动释放频率,有些列队会在执行完任务之后自动释放,有些是不会自动释放的,需要手动释放。

简单看一下列队优先级

    DispatchQueue.global(qos: .background).async {

        for i in 1...5 {

            print("background: \(i)")

        }

    }

    DispatchQueue.global(qos: .default).async {

        for i in 1...5 {

            print("default: \(i)")

        }

    }

    DispatchQueue.global(qos: .userInteractive).async {

        for i in 1...5 {

            print("userInteractive: \(i)")

        }

    }

    执行结果:

            default: 1

            userInteractive: 1

            background: 1

            default: 2

            userInteractive: 2

            background: 2

            userInteractive: 3

            default: 3

            userInteractive: 4

            userInteractive: 5

            default: 4

            background: 3

            default: 5

            background: 4

            background: 5

DispatchWorkItem

DispatchWorkItem是用于帮助DispatchQueue来执行列队中的任务。类的相关内容如下:

    public class DispatchWorkItem {

          public init(qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default, block: @escaping @convention(block) () -> Swift.Void)

          public func perform()

          public func wait()

          public func wait(timeout: DispatchTime) -> DispatchTimeoutResult

          public func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult

          public func notify(qos: DispatchQoS = default, flags: DispatchWorkItemFlags = default, queue: DispatchQueue, execute: @escaping @convention(block) () -> Swift.Void)

          public func notify(queue: DispatchQueue, execute: DispatchWorkItem)

          public func cancel()

          public var isCancelled: Bool { get }

    }

一般情况下,我们开启一个异步线程,会这样创建列队并执行async方法,以闭包的方式提交任务。

    DispatchQueue.global().async {

        // do async task

    }

但是Swift3中使用了DispatchWorkItem类将任务封装成为对象,由对象进行任务。

    let item = DispatchWorkItem {

          // do task

    }

    DispatchQueue.global().async(execute: item)

当然,这里也可以使用DispatchWorkItem实例对象的perform方法执行任务

    let workItem = DispatchWorkItem {

        // do task

    }

    DispatchQueue.global().async {

        workItem.perform()

    }

但是对比一下两种方式,显然第一种更加简洁,方便。

执行任务结束通过nofify获得通知

    let workItem = DispatchWorkItem {

        // do async task

        print(Thread.current)

    }


    DispatchQueue.global().async {

        workItem.perform()

    }


    workItem.notify(queue: DispatchQueue.main) {

        // update UI

        print(Thread.current)

    }

使用wait等待任务执行完成

    let queue = DispatchQueue(label: "queue", attributes: .concurrent)

    let workItem = DispatchWorkItem {

        sleep(5)

        print("done")

    }


    queue.async(execute: workItem)

    print("before waiting")

    workItem.wait()

    print("after waiting")


    执行结果:

            before waiting

            done

            after waiting

也可以在初始化的时候指定更多的参数

    let item = DispatchWorkItem(qos: .default, flags: .barrier) {

        // do task

    }

第一个参数同样说优先级,第二个参数指定flag

    public struct DispatchWorkItemFlags : OptionSet, RawRepresentable {

        public let rawValue: UInt

        public init(rawValue: UInt)

        public static let barrier: DispatchWorkItemFlags

        public static let detached: DispatchWorkItemFlags

        public static let assignCurrentContext: DispatchWorkItemFlags

        public static let noQoS: DispatchWorkItemFlags

        public static let inheritQoS: DispatchWorkItemFlags

        public static let enforceQoS: DispatchWorkItemFlags

    }

barrier

假如我们有一个并发的列队用来读写一个数据对象,如果这个列队的操作是读,那么可以同时多个进行。如果有写的操作,则必须保证在执行写操作时,不会有读取的操作执行,必须等待写操作完成之后再开始读取操作,否则会造成读取的数据出错,经典的读写问题。这里我们就可以使用barrier:

    let item = DispatchWorkItem(qos: .default, flags: .barrier) {

        // write data

    }

    let dataQueue = DispatchQueue(label: "com.data.queue", attributes: .concurrent)

    dataQueue.async(execute: item)

字典的读写操作

    private let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)

    private var dictionary: [String: Any] = [:]


    public func set(_ value: Any?, forKey key: String) {

        // .barrier flag ensures that within the queue all reading is done

        // before the below writing is performed and

        // pending readings start after below writing is performed

        concurrentQueue.async(flags: .barrier) {

              self.dictionary[key] = value

        }

    }


    public func object(forKey key: String) -> Any? {

        var result: Any?

        concurrentQueue.sync {

            result = dictionary[key]

        }


        // returns after concurrentQueue is finished operation

        // beacuse concurrentQueue is run synchronously

        return result

    }

通过在并发代码中使用barrier将能够保证写操作在所有读取操作完成之后进行,而且确保写操作执行完成之后再开始后续的读取操作。具体的详情看这里

延时处理

使用asyncAfter来提交任务进行延迟。之前是使用dispatch_time,现在是使用DispatchTime对象表示。可以使用静态方法now获得当前时间,然后再通过加上DispatchTimeInterval枚举获得一个需要延迟的时间。注意:仅仅是用于在具体时间执行任务,不要在资源竞争的情况下使用。并且在主列队使用。

    let delay = DispatchTime.now() + DispatchTimeInterval.seconds(10)

    DispatchQueue.main.asyncAfter(deadline: delay) {

        // 延迟执行

    }

我们可以进一步简化,直接添加时间

    let delay = DispatchTime.now() + 10

    DispatchQueue.main.asyncAfter(deadline: delay) {

        // 延迟执行

    }

因为在DispatchTime中自定义了“+”号。

public func +(time: DispatchTime, seconds: Double) -> DispatchTime

更多有关延时操作看这里

DispatchGroup

DispatchGroup用于管理一组任务的执行,然后监听任务的完成,进而执行后续操作。比如:同一个页面发送多个网络请求,等待所有结果请求成功刷新UI界面。一般的操作如下:

    let queue = DispatchQueue.global()

    let group = DispatchGroup()


    queue.async(group: group) {

        print("Task one finished")

    }

    queue.async(group: group) {

        print("Task two finished")

    }

    queue.async(group: group) {

        print("Task three finished")

    }

    group.notify(queue: queue) {

        print("All task has finished")

    }

打印如下:

Task three finished

Task two finished

Task one finished

All task has finished

由于是并发执行异步任务,所以任务的先后次序是不一定的,看起来符合我们的需求,最后接受通知然后可以刷新UI操作。但是真实的网络请求是异步、耗时的,并不是立马就返回,所以我们使用asyncAfter模拟延时看看,将任务1延时一秒执行:

    let queue = DispatchQueue.global()

    let group = DispatchGroup()


    queue.async(group: group) {

        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {

            print("Task one finished")

        })

    }

    queue.async(group: group) {

        print("Task two finished")

    }

    queue.async(group: group) {

        print("Task three finished")

    }

    group.notify(queue: queue) {

        print("All task has finished")

    }

结果却不是我们预期的那样,输出结果如下:

Task two finished

Task three finished

All task has finished

Task one finished

所以,为了真正实现预期的效果,我们需要配合group的enter和leave两个函数。每次执行group.enter()表示一个任务被加入到列队组group中,此时group中的任务的引用计数会加1,当使用group.leave() ,表示group中的一个任务完成,group中任务的引用计数减1.当group列队组里面的任务引用计数为0时,会通知notify函数,任务执行完成。注意:enter()和leave()成对出现的。

      let queue = DispatchQueue.global()

      let group = DispatchGroup()


      group.enter()

      queue.async(group: group) {

          DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {

              print("Task one finished")

              group.leave()

          })

      }


      group.enter()

      queue.async(group: group) {

          print("Task two finished")

          group.leave()

      }


      group.enter()

      queue.async(group: group) {

          print("Task three finished")

          group.leave()

      }


      group.notify(queue: queue) {

          print("All task has finished")

      }

这下OK了,输出跟预期一样。当然这里也可以使用信号量实现,后面会介绍。

Task three finished

Task two finished

Task one finished

All task has finished

信号量

对于信号量的具体内容,可以看我之前写的一篇博文。使用起来很简单,创建信号量对象,调用signal方法发送信号,信号加1,调用wait方法等待,信号减1.现在也适用信号量实现刚刚的多个请求功能。

      let queue = DispatchQueue.global()

      let group = DispatchGroup()

      let semaphore = DispatchSemaphore(value: 0)


      queue.async(group: group) {

          DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {

              semaphore.signal()

              print("Task one finished")

          })

          semaphore.wait()

      }

      queue.async(group: group) {

          DispatchQueue.main.asyncAfter(deadline: .now() + 0.8, execute: {

              semaphore.signal()

              print("Task two finished")

          })

          semaphore.wait()

      }

      queue.async(group: group) {

          print("Task three finished")

      }


      group.notify(queue: queue) {

          print("All task has finished")

      }

Suspend / Resume

Suspend可以挂起一个线程,即暂停线程,但是仍然暂用资源,只是不执行

Resume回复线程,即继续执行挂起的线程。

循环执行任务

之前使用GCD的dispatch_apply()执行多次任务,现在是调用concurrentPerform(),下面是并发执行5次

    DispatchQueue.concurrentPerform(iterations: 5) {

        print("\($0)")

    }

DispatchSource

DispatchSource提高了相关的API来监控低级别的系统对象,比如:Mach ports, Unix descriptors, Unix signals, VFS nodes。并且能够异步提交事件到派发列队执行。

简单定时器

    // 定时时间

    var timeCount = 60

    // 创建时间源

    let timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())

    timer.schedule(deadline: .now(), repeating: .seconds(1))

    timer.setEventHandler {

        timeCount -= 1

        if timeCount <= 0 { timer.cancel() }

        DispatchQueue.main.async {

            // update UI or other task

        }

    }

    // 启动时间源

    timer.resume()

对于比使用Timer的好处可以看这里

应用场景

多个任务依次执行

最容易想到的就是创建一个串行列队,然后添加任务到列队执行。

    let serialQueue = DispatchQueue(label: "com.my.queue")

    serialQueue.async {

      print("task one")

    }

    serialQueue.async {

      print("task two")

    }

    serialQueue.async {

      print("task three")

    }

其次就是使用前面讲到的DispatchGroup。

取消DispatchWorkItem的任务

直接取消任务

    let queue = DispatchQueue(label: "queue", attributes: .concurrent)

    let workItem = DispatchWorkItem {

        print("done")

    }


    DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {

        queue.async(execute: workItem) // not work

    }

    workItem.cancel()

直接调用取消,异步任务不会执行。

执行的过程中取消任务

    func cancelWork() {

        let queue = DispatchQueue.global()

        var item: DispatchWorkItem!


        // create work item

        item = DispatchWorkItem { [weak self] in

              for i in 0 ... 10_000_000 {

                  if item.isCancelled { break }

                  print(i)

                  self?.heavyWork()

              }

              item = nil    // resolve strong reference cycle

        }


        // start it

        queue.async(execute: item)


        // after five seconds, stop it if it hasn't already

        queue.asyncAfter(deadline: .now() + 5) { [weak item] in

                item?.cancel()

        }

    }

具体详情看这里,也可以了解这篇文章

注意事项

线程死锁

不要在主列队中执行同步任务,这样会造成死锁问题。

————————————————

版权声明:本文为CSDN博主「Longshihua」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/longshihua/article/details/79756676

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容