https://www.jianshu.com/p/47e45367e524

前言

在Swift4.0版本中GCD的常用方法还是有比较大的改动,这里做个简单的整理汇总。

GCD的队列

队列是一种遵循先进先出(FIFO)原则的数据结构,是一种特殊的线性表。

  主队列 全局队列 串行队列 并行队列
同步 X 并行同步 串行同步 并行同步
异步 串行异步 并行异步 串行异步 并行异步

X 表示禁止这么使用,—— 表示不建议这么使用。

1. 主队列

主队列默认是串行的,另外主队列不能结合同步函数(sync)使用,会造成线程死锁。

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib. let mainQueue = DispatchQueue.main
mainQueue.sync {
print("造成当前线程:\(Thread.current)死锁")
}
}

同时主队列中不应该添加耗时的任务,因为系统的UI相关事务都是在主线程队列中完成的,大量大耗时操作可能会造成卡顿,应该避免。

主队列最常用的方法是当子线程需要通知主线程做一些UI上面的操作时,结合子线程使用:

let queue = DispatchQueue(label: "com.roki.thread")
queue.async {
// 大量耗时操作
print("大量耗时操作线程:\(Thread.current)")
Thread.sleep(forTimeInterval: 2) DispatchQueue.main.async {
//回到主线程操作UI
print("回到主线程:\(Thread.current)")
}
}
 
DF993F5D-6A1D-4869-A7E8-5E9D871915D8.png

2. 全局队列

全局队列是由系统创建的,默认是并行的。全局队列具体运行在哪一个线程,是由系统维护一个线程池,然后挑选其中的一至多条线程来使用。哪条线程会被使用是未知的,是由系统根据当前的并发任务,处理器的负载等情况来决定的。

  • 全局并发同步队列
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib. for i in 1...10 {
DispatchQueue.global().sync {
//全局并发同步
Thread.sleep(forTimeInterval: 2)
print("线程\(Thread.current)正在执行\(i)号任务")
}
}
}
 
9C11F7B3-7602-4E3B-94A2-0255DFC77077.png

从终端输出我们可以知道任务被顺序执行了,这是因为虽然当前是一个并发队列,但是是同步执行的。同步操作会使得在前一个任务完成后才去执行下一个任务。同步与异步的区别还在于它不会创建新的线程,而是直接在当前线程中执行了相关的任务,当前线程是主线程。同步会阻塞当前线程。

  • 全局并发异步队列
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib. for i in 1...10 {
DispatchQueue.global().async {
//全局并发异步
Thread.sleep(forTimeInterval: 2)
print("线程\(Thread.current)正在执行\(i)号任务")
}
}
}
 
7D63F60B-3DC4-4071-854E-CCB3E36019E4.png

从终端输出我们可以知道任务被随机执行了,而且被分配在多个子线程中执行的,这符合并发的本质。另外需要注意的是全局并发异步队列,系统在挑选来执行任务的线程的时候,会挑选除了主线程之外的其他线程。

3. 自定义队列

除了上述队列之外,我们还可以使用DispatchQueue创建自定义的队列。
let queue = DispatchQueue(label: "com.roki.thread")
需要注意的是上述创建自定义队列的方式,默认创建的是串行队列。
还有一种创建自定义队列的方法是:

let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent)

iOS10.0 之后上述API更新为:

let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: .workItem, target: nil)

参数说明:

  1. label 表示队列标签

  2. qos 表示设置队列的优先级

    • .userInteractive 需要用户交互的,优先级最高,和主线程一样
    • .userInitiated 即将需要,用户期望优先级,优先级高比较高
    • .default 默认优先级
    • .utility 需要执行一段时间后,再通知用户,优先级低
    • *.background 后台执行的,优先级比较低
    • *.unspecified 不指定优先级,最低
  3. attributes 表示队列类型,默认为串行队列,设置为.concurrent表示并行队列。iOS 10.0之后 attributes 新增.initiallyInactive属性表示当前队列是不活跃的,它需要调用DispatchQueueactivate方法来执行任务。

  4. autoreleaseFrequency 表示自动释放频率,设置自动释放机制。

    • .inherit 表示不确定,之前默认的行为也是现在的默认值
    • .workItem 表示为每个执行的项目创建和排除自动释放池, 项目完成时清理临时对象
    • .never 表示GCD不为您管理自动释放池
  • 同步串行队列
    其实同步串行队列,没什么意思的,不管是同步操作还是串行操作都会导致任务被一个一个的执行。这个操作尤其是在主线程执行的时候需要注意,避免造成线程的卡顿。
let queue = DispatchQueue(label: "com.custom.thread")
queue.sync {
//同步串行队列
}
  • 异步串行队列
    因为是串行队列,即使是异步执行的,任务也是按照顺序依次执行的,但是在子线程中执行的。
let queue = DispatchQueue(label: "com.custom.thread")
queue.async {
//异步串行队列
}
 
9D774A76-4F71-49AE-903A-57741B89D1DE.png

根据iOS10.0 之后attributes新增的.initiallyInactive属性,我们可以创建不活跃队列。

  • 同步串行不活跃队列
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.initiallyInactive, autoreleaseFrequency: .workItem, target: nil)
queue.sync {
//同步串行不活跃队列
}
queue.activate()
  • 异步并行不活跃队列
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: [.initiallyInactive, .concurrent], autoreleaseFrequency: .workItem, target: nil)
queue.async {
//异步并行不活跃队列
}
queue.activate()
  • 同步并行队列
    只要涉及到同步的,都不会开启新线程,会在当前线程执行任务,同时任务只能依次执行。
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent)
for i in 1...10 {
queue.sync {
//并发同步队列
Thread.sleep(forTimeInterval: 2)
print("线程\(Thread.current)正在执行\(i)号任务")
}
}
 
BC7AAE0B-C3FA-4AEB-9593-72E02D4A105F.png
  • 异步并行队列
    异步并行队列就会在多个线程中,随机执行任务。
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent)
for i in 1...10 {
queue.async {
//并发异步
Thread.sleep(forTimeInterval: 2)
print("线程\(Thread.current)正在执行\(i)号任务")
}
}
 
EA277C08-A7FF-49B5-B26B-73521A51C5CF.png

4. 任务组(DispatchGroup)

如果我们想监听多个任务的执行情况,那么我们需要将任务(异步、同步、串行、并行)都添加到任务组中,然后通过DispatchGroupnotify函数就可以监听是否组内任务都已经完成。

let group = DispatchGroup()
let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent)
for i in 1...10 {
queue.async(group: group) {
//并发异步
Thread.sleep(forTimeInterval: 2)
print("线程\(Thread.current)正在执行\(i)号任务")
}
} group.notify(queue: DispatchQueue.main) {
// 通知主线程,子线程操作已完成
print("所有任务都已经完成")
}
 
05A6287E-81AC-49A0-A277-D53994DB8E0A.png

5. 任务对象(DispatchWorkItem)

在Swift4.0 中使用DispatchWorkItem代替原来OC中的dispatch_block_t。 在DispatchQueue执行操作,除了直接传了一个() -> Void 类型的闭包外,还可以传入一个DispatchWorkItem任务对象。DispatchWorkItem的初始化方法可以配置QosDispatchWorkItemFlags,但是这两个参数都有默认参数,所以也可以只传入一个闭包。
DispatchWorkItemFlags枚举中assignCurrentContext表示QoS根据创建时的context决定。 值得一提的是DispatchWorkItem也有wait方法,使用方式和DispatchGroup一样。调用会等待这个workItem执行完。

let queue = DispatchQueue(label: "com.custom.thread", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent)
let workItem = DispatchWorkItem {
Thread.sleep(forTimeInterval: 2)
print("线程\(Thread.current)正在执行任务")
}
queue.async(execute: workItem) print("before waiting")
workItem.wait()
print("after waiting")
 
A25890FA-C5B3-4000-94D1-D00044663A1A.png
 
 

简介

Grand Central Dispatch (GCD) 是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。

众所周知,GCD, NSOperationQueue, NSThread, pthread是iOS中多线程的几种处理方式,Swift3之前GCD仍是面向过程的写法,所以需要封装一层再使用。Swift3苹果打成Dispatch这个module.你可以通过import进行导入再使用。Swift4,直接使用。

特性

GCD可用于多核的并行运算
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

用法

  1. 异步执行回主线程写法
DispatchQueue.global().async {
print("async do something\(Thread.current)")
DispatchQueue.main.async {
print("come back to main thread\(Thread.current)")
}
}
  1. 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参数设置。

  1. DispatchWorkItem
let dispatchWorkItem = DispatchWorkItem(qos: .default, flags: .barrier) {
<#code#>
}
  1. after
let deadline = DispatchTime.now() + 5.0
DispatchQueue.global().asyncAfter(deadline: deadline) {
<#code#>
}
  1. 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")
}
  1. 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()
}
}
  1. 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")
}
}
 
 
 
 
 
 

【iOS】Swift4.0 GCD的使用笔记的更多相关文章

  1. iOS阶段学习第四天笔记(循环)

    iOS学习(C语言)知识点整理笔记 一.分支结构 1.分支结构分为单分支 即:if( ){ } ;多分支 即:if( ){ }else{ }  两种 2.单分支 if表达式成立则执行{ }里的语句:双 ...

  2. iOS阶段学习第三天笔记(运算符)

    iOS学习(C语言)知识点整理笔记 1.运算符 一.算术运算符 1)表达式由变量.常量.运算符构成,有确定的类型和值 2)算术运算符包括: +(加),-(减),*(乘),/(除),%(模) 3)算术运 ...

  3. ios开发多线程--GCD

    引言 虽然GCD使用很广,而且在面试时也经常问与GCD相关的问题,但是我相信深入理解关于GCD知识的人肯定不多,大部分都是人云亦云,只是使用过GCD完成一些很简单的功能.当然,使用GCD完成一些简单的 ...

  4. IOS任务管理之GCD使用

    前言: 前天学了IOS的NSOperation基本使用,我们得知NSOperation也是基于IOS GCD(Grand Central Dispatch)实现,其实在做IOS开发中GCD已经基本上能 ...

  5. [iOS]深入理解GCD

    看到一篇很好的文章,本来想翻译的,但发现已经有人翻译了,遂简单整理了一下,方便阅读学习 新博客[wossoneri.com] 什么是GCD GCD(Grand Central Dispatch)是li ...

  6. iOS 阶段学习第四天笔记(循环)

    iOS学习(C语言)知识点整理笔记 一.分支结构 1.分支结构分为单分支 即:if( ){ } ;多分支 即:if( ){ }else{ }  两种 2.单分支 if表达式成立则执行{ }里的语句:双 ...

  7. iOS 阶段学习第三天笔记(运算符)

    iOS学习(C语言)知识点整理笔记 1.运算符 一.算术运算符 1)表达式由变量.常量.运算符构成,有确定的类型和值 2)算术运算符包括: +(加),-(减),*(乘),/(除),%(模) 3)算术运 ...

  8. iOS 开发之 GCD 不同场景使用

    header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em} .markdown-body{overflow:hidden} .markdo ...

  9. iOS 开发之 GCD 基础

    header{font-size:1em;padding-top:1.5em;padding-bottom:1.5em} .markdown-body{overflow:hidden} .markdo ...

随机推荐

  1. lnmp一键安装的缺点???

    自己尝试几次在新的服务器上安装lnmp环境,每次都是一键安装,然后在/home/wwwroot/default目录下放对应的PHP文件就Ok了,很方便.其实也尝试过用命令行,就是很多命令的那种安装,中 ...

  2. 应用间的API访问如何认证?

    任何一个一个应用要访问另一个应用的API,需要首先到开放平台获取访问accesskey, 然后访问目标应用,目标应用中先检查来源访问token是否已存在缓存中,不存在需要去开放平台校验accesske ...

  3. 标准模板库中的栈(stack)

    ////C++数据结构与算法(第4版) Adam Drozdek 著  徐丹  吴伟敏<<清华大学出版社>> STL中的通用栈类实现为容器适配器:使用以指定方式运行的容器.栈容 ...

  4. require - 引入文件

    导入 /** * Creates the node for the load command. Only used in browser envs. */ req.createNode = funct ...

  5. 用绿色版TOMCAT和绿色版JDK安装一个WEB服务器

    (1) 使用绿色版本JDK,解压到一个目录上D:\jdk1.6.   (2) 使用绿色版本Tomcat,解压到另一个目录上D:\jdk1.6\tomcat5.5 只要在bat文件D:\tomcat5. ...

  6. centos 搭建 git 服务端和客户端

    centos 搭建git需要设置远程服务端和客户端.远程代码存放在服务端,多个客户端可以共享和维护服务端代码. 一.服务端主机 1.创建ssh,大部分默认已经安装,有ssh就跳过 yum instal ...

  7. leetcode142 Linked List Cycle II

    """ Given a linked list, return the node where the cycle begins. If there is no cycle ...

  8. Matplotlib 多个图形

    章节 Matplotlib 安装 Matplotlib 入门 Matplotlib 基本概念 Matplotlib 图形绘制 Matplotlib 多个图形 Matplotlib 其他类型图形 Mat ...

  9. Censoring「USACO 2015 Feb」

    题目描述 有一个S串和一个T串,长度均小于1,000,000,设当前串为U串,然后从前往后枚举S串一个字符一个字符往U串里添加,若U串后缀为T,则去掉这个后缀继续流程. 输入格式 包含两行,第一行为S ...

  10. Ubuntu操作系统编写zabbix的启动管理脚本

    Ubuntu操作系统编写zabbix的启动管理脚本 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.修改zabbix的pid存放路径 1>.创建存放zabbix的pid目录 ...