swift之GCD基础
1.主队列
//获取主队列
let mainQueue = DispatchQueue.main
2.串行队列
- 创建时指定label 便于调试,一般使用bundle Identifier类似的命名方式
let queue = DispatchQueue(label: "com.syc.nd")
3.并行队列
//获取全局并发队列
let globleQueue = DispatchQueue.global()
//第二种
let queue = DispatchQueue(label: "com.xxx.xxx.queueName", attributes: .concurrent)
let concurrentQueue = DispatchQueue.init(label: "concurrentQueue", qos: DispatchQoS.default, attributes: [.concurrent, .initiallyInactive], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.never, target: DispatchQueue?.none)
3.1创建队列方法参数解释
convenience init(label: String, qos: DispatchQoS = .unspecified, attributes: DispatchQueue.Attributes = [], autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency = .inherit, target: DispatchQueue? = nil)
-
label: 队列名称id
-
qos:(服务质量等级)队列的优先级,如果没有指定值,会使用默认的属性 unspecified (不指定优先级)
- 优先级排列: userInteractive > userInitiated > default > utility > background > unspecified
- 优先级:
public static let userInteractive: DispatchQoS 用户交互 public static let userInitiated: DispatchQoS 用户发起 public static let
default: DispatchQoS 默认优先级 public static let utility: DispatchQoS 对应oc中的low 低 public static let background: DispatchQoS 后台 public static let unspecified: DispatchQoS 不指定优先级
-
attributes: 选项集合 包含两个选项
- concurrent: 表示队列为并行队列
- initiallyInactive: 标识队列中的任务需要手动触发,由队列的activate方法来触发 如果未添加此标识,向队列中添加的任务会自动运行,如果不设置该值 表示创建的是串行队列, 如果希望创建的是并行队列,并且需要手动触发的话,需要添加值 [.concurrent, .initiallyInactive] 如果不需要的话 直接传[]
-
**autoreleaseFrequency:**类型为枚举类型 用来设置管理任务内对象生命周期的 autorelease pool 的自动释放频率 包含三个类型:
- inherit: 集成目标队列的该属性
- workItem: 跟随每个任务的执行周期进行自动创建和是释放
- never: 不会自动创建 autorelease pool 需要自己手动管理
- 一般采用workItem就可以了,如果任务中需要大量重复创建对象,可以使用never属性,来手动创建 autorelease pool
-
target: 这个参数设置了队列的目标队列,即队列中的任务运行时实际所在的队列。目标队列最终约束了队列的优先级等属性。
在程序中手动创建的队列最后都指向了系统自带的主队类或全局并发队列
那为什么不直接将任务添加到系统队列中,而是自定义队列呢?这样的好处是可以将任务分组管理。如单独阻塞某个队列中的任务,而不是阻塞系统队列中的全部任务。如果阻塞了系统队列,所有指向它的原队列也就被阻塞。
4.同步任务(串行队列)
4.1串行队列中新增同步任务
- 会阻塞当前线程,必须要等待当前任务执行完成之后,才能往下面执行,可以理解成这个同步任务把主线程阻塞了,让自己优先插队执行。
func simpleSQueue() -> Void {
let queue = DispatchQueue(label: "com.syc.nd")
//同步执行队列
queue.sync {
for i in 1...3 {
print("同步执行队列---😄",i)
}
}
//同步主队列执行
for j in 100...103 {
print("同步主队列---😭",j);
}
}
输出结果:
4.2串行队列中嵌套本队列的同步任务
- 主队列中不允许添加一个同步任务,否则会发生死锁程序崩溃
func testSyncInSerialQueue() -> Void {
let serialQueue = DispatchQueue(label: "com.SerialQueue")
//异步执行
serialQueue.async {
for i in 1...3 {
print("异步执行队列---👀 current Thread====",i,Thread.current)
}
serialQueue.sync {
print("串行队列中添加本队列的同步任务=====")
}
}
print("end test")
}
执行到嵌套任务程序的时候崩溃了,这是死锁导致的。这里死锁是由两个因素导致:串行队列、同步任务,回顾一下串行队列的特性就好解释了:串行队列中执行任务的线程不允许被当前队列中的任务阻塞。下个例子我们试试:并行队列 + 同步任务,看看会不会导致死锁
4.3并行队列中嵌套本队列的同步任务
func testAsyncInConcurrent() -> Void {
let concurrentQueue = DispatchQueue(label: "com.concurrentQueue", attributes: [.concurrent])
concurrentQueue.async {
print("异步执行函数=====")
concurrentQueue.sync {
print("同步函数执行====")
}
}
}
执行结果很顺利,而且印证了同步函数的另一个特性:同步函数直接在当前线程运行
4.4.串行队列中嵌套其他队列的同步任务
let serialQueue = DispatchQueue(label: "com.SerialQueue")
let serialQueue2 = DispatchQueue(label: "com.SerialQueue2")
serialQueue.sync {
print("同步函数执行====")
serialQueue2.sync {
print("串行队列中嵌套其他队列的同步任务")
}
}
执行结果,串行队列嵌套的同步任务执行成功了,和前面的例子不一样啊。是的,因为这里嵌套的是另一个队列的任务,虽然它们都运行在同一个线程上,一个串行队列可以对另一个串行队列视而不见。不同队列复用线程这是系统级的队列作出的优化,但是在同一个串行队列内部,任务一定都是按顺序执行的,这是自定义队列的最本质作用
5.异步任务
- 任务提交后不会阻塞当前线程,会由队列安排另外一个线程执行。
5.1并行队列中新增异步任务
func testAsyncInConcurrent2() -> Void {
let concurrentQueue = DispatchQueue(label: "com.concurrentQueue", attributes: [.concurrent])
concurrentQueue.async {
print("并行队列中 增加异步任务")
}
}
执行结果: 开辟了一个新县城在执行任务
5.2 串行队列中新增异步任务
func testAsyncInSerial() -> Void {
let serialQueue = DispatchQueue(label: "com.serialQueue", attributes: [.concurrent])
serialQueue.async {
print("串行队列中 增加异步任务 ===== ",Thread.current)
}
}
执行结果依旧是开辟一个新的 线程来执行
5.3串行队列嵌套本队列的异步任务
func testAsyncTaskNestedInSameSerialQueue() -> Void {
let serialQueue = DispatchQueue(label: "com.serialQueue", attributes: [.concurrent])
serialQueue.sync {
print("串行队列中 同步任务 ===== ",Thread.current)
serialQueue.async {
print("串行队列中嵌套本队列的异步任务 ===== ",Thread.current)
}
}
}
执行结果:
这个例子在一次刷新了对串行队列的认识:串行队列并不是只能运行一个线程第一层的同步任务运行在主线程上,第二层的异步任务运行在其他线程上,但它们在时间片上是分开的。这里再严格定义一下:串行队列同一时间只会运行一个线程,只有碰到异步任务时,才会使用不同于当前的线程,但都是按时间顺序执行,只有前一个任务完成了,才会执行下一个任务。
这里我们总结下队列和任务的特性:
- 串行队列同一时间只会使用同一线程、运行同一任务,并严格按照任务顺序执行。
- 并行队列同一时间可以使用多个线程、运行多个任务,执行顺序不分先后。
- 同步任务会阻塞当前线程,并在当前线程执行。
- 异步任务不会阻塞当前线程,并在与当前线程不同的线程执行。
- 如何避免死锁:不要在串行或主队列中嵌套执行同步任务。
6.其他常用任务
6.1Diapatch_barrier(栅栏任务)
栅栏任务的主要特性是可以对队列中的任务进行阻隔,执行栅栏任务时,它会先等队里中已有的任务全部执行完成之后,然后它在执行,在它之后加入的任务也必须要等到栅栏任务执行完成后才能执行
这个特性更适合并行队列,而且对栅栏任务使用同步或异步方法效果相同。
- 创建方式,先创建
DispatchWorkItem
对象(DispatchWorkItem是一个代码块,它可以被分到任何的队列,包含的代码可以在后台或主线程中被执行,简单来说:它被用于替换我们前面写的代码block来调用)标记为barrier
, 在添加到队列中
func barrir() -> Void {
let queue = DispatchQueue(label: "queueBarrir", attributes: [.concurrent])
/**
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
*/
let task = DispatchWorkItem(qos: DispatchQoS.default, flags: DispatchWorkItemFlags.barrier) {
print("实现栅栏任务")
}
queue.async {
print("任务1=======",Thread.current)
}
queue.async {
print("任务2=======",Thread.current)
}
queue.async(execute: task)
queue.async {
print("任务3=======",Thread.current)
}
queue.async {
print("任务4=======",Thread.current)
}
}
栅栏使用异步函数的执行结果:
同步函数执行结果:
不管是同步还是 异步都起到了相同的效果
6.2DispatchQueue.concurrentPerform(迭代任务)
- 并行队列利用多个线程执行任务,可以提高程序执行的效率。而迭代任务可以更高效地利用多核性能,它可以利用 CPU 当前所有可用线程进行计算(任务小也可能只用一个线程)。如果一个任务可以分解为多个相似但独立的子任务,那么迭代任务是提高性能最适合的选择。
//1.单独创建 iterations: 为迭代次数 可以修改
DispatchQueue.concurrentPerform(iterations: 10) { (index) in
print("current thread====== ",Thread.current, index)
}
执行结果:
会开启新的线程来执行任务
- 迭代任务也可以放在指定的队列中
DispatchQueue.global().async {
DispatchQueue.concurrentPerform(iterations: 5) { (index) in
print("current thread====== ",Thread.current, index)
}
}
执行结果:
6.3Dispatch_After(延迟任务)
- 有时候你并不需要立刻将任务加入队列中执行,而需要等待一段时间之后再进入队列中,这时候可以使用
asyncAfter
//延迟执行函数 ()
func dispatch_later(_ time: TimeInterval, block: @escaping ()-> ()) -> Void {
print("任务在\(time)秒后执行",Date())
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + time, execute: block)
}
//调用
dispatch_later(2) {
print("dispatch_after 延迟执行任务====",Thread.current,Date())
}
6.4Dispatch_Group(任务组)
任务组相当于一系列的松散集合,它可以来自相同或不同的队列,扮演者组织者角色。他可可易通知外部队列,组内的任务是否已经完成。或则阻塞当前的线程,知道组内的任务都完成。所有适合组队执行的任务都可以使用任务组,且任务组更适合集合异步任务(如果都是同步任务,直接使用串行队列即可)
- 创建任务组
let queueGroup = DispatchGroup()
6.4.1 添加任务到指定任务组
func group() -> Void {
let queueGroup = DispatchGroup()
let queue = DispatchQueue.global()
queue.async(group: queueGroup, execute: DispatchWorkItem(block: {
print("任务1-----",Thread.current)
}))
queue.async(group: queueGroup, execute: DispatchWorkItem(block: {
print("任务2-----",Thread.current)
}))
queue.async(group: queueGroup, execute: DispatchWorkItem(block: {
print("任务3-----",Thread.current)
}))
queue.async(group: queueGroup, execute: DispatchWorkItem(block: {
print("任务4-----",Thread.current)
}))
queueGroup.notify(queue: DispatchQueue.main) {
print("任务组中所有的任务都完成了,可以执行下步操作-----")
}
}
执行结果
6.4.2 使用Group.enter()
、Group.leave()
配对方法,表示任务加入任务组
func group2() -> Void {
let queueGroup = DispatchGroup()
let queue = DispatchQueue.global()
queueGroup.enter()
queue.async {
print("任务1-----",Thread.current)
queueGroup.leave()
}
queueGroup.enter()
queue.async {
print("任务2-----",Thread.current)
queueGroup.leave()
}
queueGroup.notify(queue: DispatchQueue.main) {
print("任务组中所有的任务都完成了,可以执行下步操作-----")
}
}
执行结果:
两种加入方式在对任务处理的特性上是没有区别的,只是便利之处不同。如果任务所在的队列是自己创建或引用的系统队列,那么直接使用第一种方式直接加入即可。如果任务是由系统或第三方的 API 创建的,由于无法获取到对应的队列,只能使用第二种方式将任务加入组内,例如将 URLSession
的 addDataTask
方法加入任务组中:
extension URLSession {
func addDataTask(to group: DispatchGroup,
with request: URLRequest,
completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void)
-> URLSessionDataTask {
group.enter() // 进入任务组
return dataTask(with: request) { (data, response, error) in
completionHandler(data, response, error)
group.leave() // 离开任务组
}
}
}
6.4.3任务通知
group.notify
方法,它可以在所有的任务完成之后,通知指定队列并执行一个指定任务,这个通知的操作是异步的(以为这后续代码不需要等待任务,可以继续执行)代码示例如上group.wait
方法,它会在所有任务完成之后,在执行当前线程,因此这个操作是起到阻塞的作用:
func group3() -> Void {
let queueGroup = DispatchGroup()
let queue = DispatchQueue.global()
queueGroup.enter()
queue.async {
print("任务1-----",Thread.current)
queueGroup.leave()
}
queueGroup.enter()
queue.async {
print("任务2-----",Thread.current)
queueGroup.leave()
}
queueGroup.wait()
print("任务组中所有的任务都完成了,可以执行下步操作-----")
}
执行结果:
wait
方法中还可以指定具体时间,它表示等待不超过这个时间,如果任务组在指定的时间内完成任务则立即回复当前线程,否则将等待到时间结束后再回复当前线程。
- 方式1,使用
DispatchTime
,它表示一个时间间隔,精确到纳秒(1/1000,000,000秒):
//表示等待2秒 表示从当前时间开始后 2 秒,数字字面量也可以改为使用 TimeInterval 类型变量
queueGroup.wait(timeout: DispatchTime.now() + 2)
- 方式2,使用 DispatchWallTime,它表示当前的绝对时间戳,精确到微秒(1/1000,000 秒),通常使用字面量即可设置延时时间,也可以使用 timespec 结构体来设置一个精确的时间戳
// 使用字面量设置
var wallTime = DispatchWallTime.now() + 2.0 // 表示从当前时间开始后 2 秒,数字字面量也可以改为使用 TimeInterval 类型变量
7.队列的挂起和唤醒
GCD提供了一套机制,可以挂起队列中尚未执行的任务,已经执行的任务会继续执行,后续还可以手动在唤醒队列
这两个方法属于Dispatchobject
对象的方法,而这个对象是DispatchQueue
、DispatchGroup
、DispatchSource
、DispatchIO
、DispatchSemaphore
这几个类的父类,但这两个方法只有DispatchQueue
、DispatchSource
支持,调用时需注意
挂起使用suspend()
,唤醒使用resume()
。对于队列,这两个方法调用时需要配对,因为可以多次挂起,调用唤醒的次数等于挂起的次数才能生效,唤醒的次数更多则会报错,所以使用时最好设计一个计数器,或者封装一个挂起,唤醒的方法,在放发内部进行检查。
而对于DispatchSource
则有多不同,它必须先调用resume()
才能接受信息,所以此时唤醒的数量等于挂起的数量加一。
下面我们通过例子看看实现:
class SuspendAndResum: NSObject {
/// 创建一个并发队列
let concurrentQueue = DispatchQueue(label: "com.concurrentQeueu", attributes: [.concurrent])
/// 挂起的次数
var suspendCount = 0
//MARK: -- 队列方法
/// 挂起队列测试方法
func suspendQueue() -> Void {
print("suspend queue start test")
concurrentQueue.async {
print("任务1concurrentQueue.async ======",Thread.current)
}
concurrentQueue.async {
print("任务2concurrentQueue.async ======",Thread.current)
}
//添加栅栏任务
let workeitem = DispatchWorkItem(qos: DispatchQoS.unspecified, flags: DispatchWorkItemFlags.barrier) {
print("add barrire =======",Thread.current)
self.safeSuspend(self.concurrentQueue)
}
concurrentQueue.async(execute: workeitem)
concurrentQueue.async {
print("任务3concurrentQueue.async ======",Thread.current)
}
concurrentQueue.async {
print("任务4concurrentQueue.async ======",Thread.current)
}
print("suspend queue end test")
}
/// 唤醒队列测试方法
func resumeQueue() -> Void {
safeResume(concurrentQueue)
}
/// 安全挂起
/// - Parameter queue: <#queue description#>
func safeSuspend(_ queue: DispatchQueue) -> Void {
//记录挂起次数
suspendCount += 1
queue.suspend()
print("任务挂起了")
}
/// 安全唤醒
/// - Parameter queue: <#queue description#>
func safeResume(_ queue: DispatchQueue) -> Void {
if suspendCount == 1 {
queue.resume()
suspendCount = 0
print("任务唤醒了")
} else if suspendCount < 1 {
print("唤醒任务次数过多-----")
} else {
queue.resume()
suspendCount -= 1
print("唤醒次数不够,还需要\(suspendCount)次唤醒")
}
}
}
执行操作:
/// 挂起
/// - Parameter sender: <#sender description#>
@IBAction func suspendbtnClick(_ sender: UIButton) {
suspendAndResume.suspendQueue()
}
/// 唤醒
/// - Parameter sender: <#sender description#>
@IBAction func resumeBtnClick(_ sender: UIButton) {
suspendAndResume.resumeQueue()
}
//创建队列的挂起和唤醒类
private lazy var suspendAndResume = SuspendAndResum()
8.DispatchSource
GCD中提供了一个DispatchSource
类,它可以帮你监听到系统底层的一些对象活动,例如这些对象:Mach port
、Unix descriptor
、Unix signal
、VFS node
,并允许你在这些活动发生时,向队列提交一个任务以进行一步处理。
这些可监听的对象都有具体的类型,你可以使用DispatchSource
的类方法来构建这些类型,这里就不一一举例。下面以文件监听类为例说明DispatchSource
的用法。
例子中监听了一个指定目录下文件的写入事件,创建监听主要有几个步骤:
- 通过
makeFileSystemObjectSource
方法创建 source - 通过
setEventHandler
设定事件处理程序,setCancelHandler
设定取消监听的处理。 - 执行
resume()
方法开始接收事件
class DispatchSourceTest: NSObject {
var filePath: String = "\(NSTemporaryDirectory)"
var counter = 0
let queueGloble = DispatchQueue.global()
override init() {
super.init()
}
func startObserve(closure: @escaping () -> Void) -> Void {
let fileURL = URL(fileURLWithPath: filePath)
let monitoredDirectoryFileDescriptor = open(fileURL.path, O_EVTONLY)
let source = DispatchSource.makeFileSystemObjectSource(
fileDescriptor: monitoredDirectoryFileDescriptor,
eventMask: .write, queue: queueGloble)
source.setEventHandler(handler: closure)
source.setCancelHandler {
close(monitoredDirectoryFileDescriptor)
}
source.resume()
}
func changeFile() {
DispatchSourceTest.createFile(name: "DispatchSourceTest.md", filePath: NSTemporaryDirectory())
counter += 1
let text = "\(counter)"
try! text.write(toFile: "\(filePath)/DispatchSourceTest.md", atomically: true, encoding: String.Encoding.utf8)
print("file writed.")
}
static func createFile(name: String, filePath: String){
let manager = FileManager.default
let fileBaseUrl = URL(fileURLWithPath: filePath)
let file = fileBaseUrl.appendingPathComponent(name)
print("文件: \(file)")
// 写入 "hello world"
let exist = manager.fileExists(atPath: file.path)
if !exist {
let data = Data(base64Encoded:"aGVsbG8gd29ybGQ=" ,options:.ignoreUnknownCharacters)
let createSuccess = manager.createFile(atPath: file.path,contents:data,attributes:nil)
print("文件创建结果: \(createSuccess)")
}
}
}
8.1 DispatchSource中定时器
- 通过
makeTimerSource()
方法或makeTimerSource(flags: DispatchSource.TimerFlags = [], queue: DispatchQueue? = nil) -> DispatchSourceTimer
两个放发来创建定时器,前者直接创建,后者可以指定在哪个队列 - GCD定时器,不会因为滑动屏幕而停止执行
- 如果定时是局部变量,声明周期太短 不会执回调,需要全局定义
8.1.1 makeTimerSource()
直接创建
//GCD定时器
timer = DispatchSource.makeTimerSource()
/**
@param : deadline 延迟时间(多久时间以后开始执行)
@param : repeating 重复执行时间 DispatchTimeInterval类型时间
@param :leeway 误差时间 0: 表示没有误差
*/
timer?.schedule(deadline: DispatchTime.now(), repeating: DispatchTimeInterval.seconds(Int(2)), leeway: DispatchTimeInterval.microseconds(0))
//没有设置重复时间的默认是执行一次
//timer?.schedule(deadline: DispatchTime.now())
timer?.setEventHandler {
print("定时器处理器执行block=====",Thread.current)
}
//执行定时器
timer?.resume()
执行结果:
8.1.2带参数创建
func dispatchTimer2() -> Void {
//GCD定时器
timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.global())
timer?.schedule(deadline: DispatchTime.now(), repeating: DispatchTimeInterval.seconds(Int(2)), leeway: DispatchTimeInterval.microseconds(0))
timer?.setEventHandler {
print("定时器处理器执行block=====",Thread.current)
}
timer?.resume()
}
func dispatchTimer2() -> Void {
//GCD定时器
timer = DispatchSource.makeTimerSource(flags: [], queue: DispatchQueue.main)
timer?.schedule(deadline: DispatchTime.now(), repeating: DispatchTimeInterval.seconds(Int(2)), leeway: DispatchTimeInterval.microseconds(0))
timer?.setEventHandler {
print("定时器处理器执行block=====",Thread.current)
}
timer?.resume()
}
上述结果表示,创建GCD定时器,只要不指定是在主队列中执行,一律都会开辟线程在子线程中执行,需要注意更新UI 一定要回到主线程