iOS学习笔记程序猿阵线联盟-汇总各类技术干货iOS开发技术分享

Swift GCD

2018-03-19  本文已影响24人  西南柯北

一、DispatchQueue

DispatchQueue 分为串行和并发,它的完整初始化方法为:

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

可见,这些参数中,除了label,其它都有默认值(label表示该队列的标签,建议传值为反向域名字符串,如:com.onevcat.Kingfisher.Animator.preloadQueue)。

当除label外的参数都使用默认值时,初始化方法返回的便是串行队列。如果需要返回并发队列,参数attributes传值为.concurrent即可。DispatchQueue.Attributes 是一个结构体类型,该结构体提供了两个静态变量:concurrentinitiallyInactive(注意,没有代表串行队列的静态变量)。如果attributes参数传值为initiallyInactive, 任务不会自动执行,而是需要开发者手动调用activate()触发。但是代码依然是串行进行的,如果想要手动触发、并行执行任务,可以指定attributes参数接受一个数组: [.concurrent, .initiallyInactive]

参数qos代表队列执行的优先级,有六种优先级可供选择:

unspecified
background
default
utility
userInteractive
userInitiated

优先级从高到低依次为userInteractive>userInitiated>utility>background, 而default与unspecified介于userInteractive与background之间,具体有系统决定。

DispatchQueue.AutoreleaseFrequency有三种属性值.inherit.workItem.never
.inherit:不确定,之前默认的行为也是现在的默认值
.workItem:为每个执行的任务创建自动释放池,项目完成时清理临时对象
.never:GCD不为您管理自动释放池

参数target 用于指定即将创建的队列与队列target优先级相同。也可通过setTarget(queue: DispatchQueue?)函数指定与queue相同的优先级。

除了开发者自己创建队列,还可以通过DispatchQueue.main获取主队列(主队列也属于串行队列)、DispatchQueue.global(qos: DispatchQoS.QoSClass) 获取全局并发队列。

创建好了队列,通过sync { /*任务*/ }async { /*任务*/ } 将任务追加到队列中。串行队列或并发队列与sync或async组合总结:

串行队列 + sync : 队列中的任务在当前线程中依次执行,后面追加的任务会等到前面追加的任务执行完了才开始执行,不开新线程。当前线程取任务执行的队列不能与该串行队列相同,否则会发生线程死锁。
串行队列(非主队列) + async : 队列中的任务在新线程中依次执行。
主队列 + async : 将任务追加到主队列,当主队列中的其他任务执行完之后才会执行,并且在在主线程中执行。
并发队列 + sync : 队列中的任务在当前线程中依次执行。
并发队列 + async : 队列中的任务在新线程中并发执行。

不管哪种组合,队列中的任务出列的方式都是FIFO。

有时候希望追加到queue中的任务暂不执行,等待某一时刻执行,这时候可使用队列的suspend()函数和resume()函数。suspend()函数使队列的暂停计数加1,resume()函数使队列的暂停计数减一。

需要注意:
1、suspend()和resume()需要成对出现,否则会crash。
2、suspend()和resume()函数只对自己创建的队列有效,对系统提供的全局队列无效。
3、suspend()和 resume()对队列中的还未执行的任务有效,对于正在执行的任务无效。

二、DispatchGroup

在追加到DispatchQueue中的多个处理全部结束后想执行结束处理,这个时候就可用到DispatchGroup。示例如下:

let group = DispatchGroup()
let queue = DispatchQueue.global()
queue.async(group: group) {
     print("任务一")
}
queue.async(group: group) {
      print("任务二")
}
queue.async(group: group) {
     print("任务三")
}
group.notify(queue: DispatchQueue.main) {
     print("完成任务一、二、三")
}
queue.async {
     print("任务四")
}

运行结果:


其中,queue既可以是同一个队列,也可以是不同的队列,既可以是串行队列,也可以是并发队列。
另外,也可以使用group的 group.wait(timeout: DispatchTime)group.wait(wallTimeout: DispatchWallTime)函数。wait 函数的参数表示等待的时间,默认是 DispatchTime.distantFuture,表示永久等待。wait函数会阻塞当前线程,即当执行的时间到了等待的时长,才会执行后面的代码。wait函数返回值是枚举类型DispatchTimeoutResult,DispatchTimeoutResult有successtimeOut两个枚举值,分别表示在等待时长内,任务执行完成和未完成。
我们还可以通过group的enter()函数和leave()函数显式表明任务是否执行完成。代码如下:
let group = DispatchGroup()
let queue = DispatchQueue.global()
group.enter()
queue.async {
     print("任务一")
     group.leave()
}
group.enter()
queue.async {
     print("任务二")
     group.leave()
}
group.enter()
queue.async {
    print("任务三")
    group.leave()
}
group.notify(queue: DispatchQueue.main) {
    print("完成任务一、二、三")
}
queue.async {
    print("任务四")
}

运行结果:


enter()leave()必须配合使用,有几次enter就要有几次leave,否则group会一直存在。当所有enter的block都leave后,会执行dispatch_group_notify的block。

三、asyncAfter

该函数用于延时操作。代码如下:

DispatchQueue.main.asyncAfter(wallDeadline: DispatchWallTime.now()+3) {
    print("执行任务")
} 

DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+3) {
    print("执行任务")
}

注意, asyncAfter函数并不是在指定时间后执行处理,而是在指定时间后将任务追加到队列中。

asyncAfter函数的第一个参数可以是DispatchTime类型的值,也可以是DispatchWallTime类型的值。

DispatchTime 表示相对时间(相对设备启动的时间,当设备休眠时,计时也会暂停),精度为纳秒级。DispatchTime.now() 获取当前相对时间,DispatchTime.now()基于当前时间三秒后的时间,表达式中的3也可以使用DispatchTimeInterval.seconds(3)替换,或者用其他的时间单位:毫秒级DispatchTimeInterval.milliseconds(Int) 、微秒级DispatchTimeInterval.milliseconds(Int)、纳秒级DispatchTimeInterval.nanoseconds(Int)
DispatchWallTime 表示绝对时间(系统时间,设备休眠计时不暂停),精度是微秒。DispatchWallTime的用法和DispatchTime差不多。

四、DispatchWorkItem

DispatchWorkItem可以将任务封装成DispatchWorkItem对象。

let workItem = DispatchWorkItem.init {
      print("执行任务")
}

可以调用workItem的perform()函数执行任务,也可以将workItem追加到DispatchQueue或DispatchGroup中。以上所有传block的地方都可换成DispatchWorkItem对象。
DispatchQueue还可以使用notify函数观察workItem中的任务执行结束,以及通过cancel()函数取消任务。

另外,workItem也可以像DispatchGroup一样调用wait()函数等待任务完成。需要注意的是,追加workItem的队列或调用perform()所在的队列不能与调用workItem.wait()的队列是同一个队列,否则会出现线程死锁。

DispatchWorkItem的完整初始化方法:

init(qos: DispatchQoS, flags: DispatchWorkItemFlags, block: () -> Void)

DispatchQoS前面已经说过,不再赘述。DispatchWorkItemFlags类型的变量有六种:

static let assignCurrentContext: DispatchWorkItemFlags
static let barrier: DispatchWorkItemFlags
static let detached: DispatchWorkItemFlags
static let enforceQoS: DispatchWorkItemFlags
static let inheritQoS: DispatchWorkItemFlags
static let noQoS: DispatchWorkItemFlags

为了高效地读写数据库或文件,通常需要将读写处理追加到并发队列
中异步执行,为了使读写操作不会引发数据竞争的问题,写入操作不能与其他的写入操作以及包含读取任务的操作并发处理,这时便可设置flag的值为.barrier。代码如下:

let queue = DispatchQueue.init(label: "com.codansYC.queue", attributes: DispatchQueue.Attributes.concurrent)
queue.async {
     print("读数据1")
}
queue.async {
     print("读数据2")
}
let workItem = DispatchWorkItem.init(qos: DispatchQoS.default, flags: DispatchWorkItemFlags.barrier) {
     print("开始写数据------写数据完成")
}
queue.async(execute: workItem)
queue.async {
     print("读数据3")
}
queue.async {
     print("读数据4")
}

运行结果:


注意,barrier只对自己创建的并发队列才有效,对系统提供的全局并发队列无效。

五、DispatchQueue.concurrentPerform

sync函数和Dispatch Group的关联API。
DispatchQueue.concurrentPerform 会按指定次数异步执行任务,并且会等待指定次数的任务全部执行完成,即会阻塞线程。建议在子线程中使用。

DispatchQueue.global().async {
     DispatchQueue.concurrentPerform(iterations: 5) { (i) in
         print("执行任务\(i+1)")
     }
     print("任务执行完成")
}
运行结果:

六、DispatchSemaphore

信号量。用于控制访问资源的数量。比如系统有两个资源可以被利用,同时有三个线程要访问,只能允许两个线程访问,第三个会等待资源被释放后再访问。
信号量的初始化方法:DispatchSemaphore.init(value: Int),value表示允许访问资源的线程数量,当value为0时对访问资源的线程没有限制。
信号量配套使用wait()函数与signal()函数控制访问资源。
wait函数会阻塞当前线程直到信号量计数大于或等于1,当信号量大于或等于1时,将信号量计数-1, 然后执行后面的代码。signal()函数会将信号量计数+1。

信号量是GCD同步的一种方式。前面介绍过的DispatchWorkItemFlags.barrier是对queue中的任务进行批量同步处理,sync函数是对queue中的任务单个同步处理,而DispatchSemaphore是对queue中的某个任务中的某部分(某段代码)同步处理。此时将DispatchSemaphore.init(value: Int)中的参数value传入1。代码如下:

var arr = [Int]()
let semaphore = DispatchSemaphore.init(value: 1) // 创建信号量,控制同时访问资源的线程数为1
for i in 0...100 {
    DispatchQueue.global().async {
                
        /*
        其他并发操作
        */
                
        semaphore.wait() // 如果信号量计数>=1,将信号量计数减1;如果信号量计数<1,阻塞线程直到信号量计数>=1
        arr.append(i)
        semaphore.signal() // 信号量计加1
                
        /*
        其他并发操作
        */
     }
}
上一篇下一篇

猜你喜欢

热点阅读