zoukankan      html  css  js  c++  java
  • Swift4

    Swift4 - GCD的使用

    2018年03月30日 17:33:27 Longshihua 阅读数:1165

    版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/longshihua/article/details/79756676

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

         }

    }

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

    注意事项

    线程死锁

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

    特性

    GCD可用于多核的并行运算

    GCD会自动利用更多的CPU内核(比如双核、四核)

    GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

    用法

    异步执行回主线程写法

    DispatchQueue.global().async {

                print("async do something(Thread.current)")

                DispatchQueue.main.async {

                    print("come back to main thread(Thread.current)")

                }

            }

    QoS

    之前接触过Quality of Service还是在VoIP,通过QoS来标注每个通信的priority,所以这边其实是把

    DISPATCH_QUEUE_PRIORITY_HIGHT

    DISPATCH_QUEUE_PRIORITY_DEFAULT

    DISPATCH_QUEUE_PRIORITY_LOW

    DISPATCH_QUEUE_PRIORITY_BACKGROUND

    转换成了

    User Interactive 和用户交互相关,比如动画等等优先级最高。比如用户连续拖拽的计算

    User Initiated 需要立刻的结果,比如push一个ViewController之前的数据计算

    Utility 可以执行很长时间,再通知用户结果。比如下载一个文件,给用户下载进度

    Background 用户不可见,比如在后台存储大量数据

    在GCD中,指定QoS有以下两种方式

    方式一,创建一个指定QoS的queue

    let queue = DispatchQueue(label: "labelname", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit)

    方式二,在提交block的时候,指定QoS

    queue.async(group: nil, qos: .background, flags: .inheritQoS) {

                <#code#>

    }

    flags的参数有

    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

    其中关于QoS的关系,可以通过flags参数设置。

    DispatchWorkItem

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

        <#code#>

    }

    after

    let deadline = DispatchTime.now() + 5.0

            DispatchQueue.global().asyncAfter(deadline: deadline) {

                <#code#>

            }

    DispatchGroup

    DispatchGroup用来管理一组任务的执行,然后监听任务都完成的事件。比如,多个网络请求同时发出去,等网络请求都完成后reload UI。

    let group = DispatchGroup()

            group.enter()

            self.sendHTTPRequest1(params:[String: Any]) {

                print("request complete")

                group.leave()

            }

            group.enter()

            self.sendHTTPRequest1(params:[String: Any]) {

                print("request complete")

                group.leave()

            }

            group.notify(queue: DispatchQueue.main) {

                print("all requests come back")

            }

    Semaphore

    Semaphore是保证线程安全的一种方式,而且继OSSpinLock不再安全后,Semaphore似乎成为了最快的加锁的方式。

    如图

    1513585102364922.png

    let semaphore = DispatchSemaphore(value: 2)

    let queue = DispatchQueue.global()

            queue.async {

                semaphore.wait()

                self.sendHTTPRequest1(params:[String: Any]) {

                    print("request complete")

                    semaphore.signal()

                }

            }

            queue.async {

                semaphore.wait()

                self.sendHTTPRequest2(params:[String: Any]) {

                    print("request complete")

                    semaphore.signal()

                }

            }

            queue.async {

                semaphore.wait()

                self.sendHTTPRequest3(params:[String: Any]) {

                    print("request complete")

                    semaphore.signal()

                }

            }

    Barrier

    GCD里的Barrier和NSOperationQueue的dependency比较接近,C任务开始之前需要A任务完成,或者A和B任务完成。

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

            queue.async {

                self.sendHTTPRequest1(params:[String: Any]) {

                    print("A")

                }

            }

            queue.async {

                self.sendHTTPRequest2(params:[String: Any]) {

                    print("B")

                }

            }

            queue.async(flags: .barrier) {

                self.sendHTTPRequest3(params:[String: Any]) {

                    print("C")

                }

            }

    作者:SealShile

    链接:https://www.jianshu.com/p/96032a032c7c

    來源:简书

    简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

  • 相关阅读:
    Magento交易邮件常见问题
    Magento谷歌分析设置
    magento SEO优化设置
    飞凤平台示范项目
    工厂生产线测量仪器的数据下发
    某外资汽车部件工厂车床联网系统
    行云仓库管理系统的概述
    arm v5,v6,v7?
    前端开发收藏夹
    mysql 事件
  • 原文地址:https://www.cnblogs.com/sundaysme/p/10386749.html
Copyright © 2011-2022 走看看