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

來源:简书

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

Swift4 - GCD的使用的更多相关文章

  1. 【iOS】Swift4.0 GCD的使用笔记

    https://www.jianshu.com/p/47e45367e524 前言 在Swift4.0版本中GCD的常用方法还是有比较大的改动,这里做个简单的整理汇总. GCD的队列 队列是一种遵循先 ...

  2. Objective-C三种定时器CADisplayLink / NSTimer / GCD的使用

    OC中的三种定时器:CADisplayLink.NSTimer.GCD 我们先来看看CADiskplayLink, 点进头文件里面看看, 用注释来说明下 @interface CADisplayLin ...

  3. iOS 多线程之GCD的使用

    在iOS开发中,遇到耗时操作,我们经常用到多线程技术.Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法,只需定义想要执行的任务,然后添加到适当的调度队列 ...

  4. 【swift】BlockOperation和GCD实用代码块

    //BlockOperation // // ViewController.swift import UIKit class ViewController: UIViewController { @I ...

  5. 修改版: 小伙,多线程(GCD)看我就够了,骗你没好处!

    多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能.具有这种能力的系 ...

  6. GCD的相关函数使用

    GCD 是iOS多线程实现方案之一,非常常用 英文翻译过来就是伟大的中枢调度器,也有人戏称为是牛逼的中枢调度器 是苹果公司为多核的并行运算提出的解决方案 1.一次性函数 dispatch_once 顾 ...

  7. hdu1695 GCD(莫比乌斯反演)

    题意:求(1,b)区间和(1,d)区间里面gcd(x, y) = k的数的对数(1<=x<=b , 1<= y <= d). 知识点: 莫比乌斯反演/*12*/ 线性筛求莫比乌 ...

  8. hdu2588 GCD (欧拉函数)

    GCD 题意:输入N,M(2<=N<=1000000000, 1<=M<=N), 设1<=X<=N,求使gcd(X,N)>=M的X的个数.  (文末有题) 知 ...

  9. BZOJ 2820: YY的GCD [莫比乌斯反演]【学习笔记】

    2820: YY的GCD Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 1624  Solved: 853[Submit][Status][Discu ...

随机推荐

  1. 八: IO流,数据的读写传输

    IO流概括图: IO流的分类:  按流: 输入流(InputStream和Reader):从硬盘或者别的地方读入内存 输出流(OutputStream和Writer):从内存里向硬盘或别的地方输出 按 ...

  2. Ubuntu基于Apache为自己的网站开启HTTPS

    暂时放这里链接,之后整理 https://www.deanhan.cn/ubuntu-apache-https.html

  3. SpringData JPA使用JPQL的方式查询和使用SQL语句查询

    使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件, 这时就可以使用@Query注解,结合JPQL的语句方式完成查询 持久 ...

  4. LR_问题_平均响应时间解释,summary与analysis不一致----Summary Report中的时间说明

    Summary是按整个场景的时间来做平均的,最大最小值,也是从整个场景中取出来的. (1)       平均响应时间:事物全部响应时间做平均计算 (2)       90%响应时间:将事物全部响应时间 ...

  5. 密码学概述&置换密码

    密码学 概述 如何将信息进行加密,传送到接收方,接收方在进行解密获取信息,中间即使有窃听者窃听到信息也可解密破解. 密码学分类 密码编辑学(保密) 密码分析学(破译) 该破译与传统的黑客技术有一定的区 ...

  6. Ubuntu操作系统部署zabbix agent服务

    Ubuntu操作系统部署zabbix agent服务 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.下载zabbix软件包 博主推荐阅读: https://www.cnblog ...

  7. 一步步教你整合SSM框架(Spring MVC+Spring+MyBatis)详细教程重要

    前言 SSM(Spring+SpringMVC+Mybatis)是目前较为主流的企业级架构方案,不知道大家有没有留意,在我们看招聘信息的时候,经常会看到这一点,需要具备SSH框架的技能:而且在大部分教 ...

  8. Oracle删除重复数据并且只留其中一条数据

    数据库操作中,经常会因为导数据造成数据重复,需要进行数据清理,去掉冗余的数据,只保留正确的数据 一:重复数据根据单个字段进行判断 1.首先,查询表中多余的数据,由关键字段(name)来查询. sele ...

  9. centos7下安装ansible

    由于centos7预装了python,因此我们可以跳过python的安装环节(记得关闭防火墙) [root@model ~]# [root@model ~]# python --version Pyt ...

  10. php中date('Y/m/d',time())显示不对

    一. 时间不对是因为没设置时区 在xampp/php/php.ini中ctrl + f 查找date.timezone 该行默认注释,去掉 ; 修改为 date.timezone = PRC 二 上述 ...