调度队列(Dispatch Queues)
GCD提供了调度队列(Dispatch Queues)来处理被提交的任务,这些队列负责管理你提交给GCD的任务并且按照先进先出(FIFO)的顺序执行它们。调度队列有以下3种:
主队列(Main queue)
提交的任务执行在主线程(main thread),串行(Serial)队列,用来更新UI。并行队列(Concurrent Queues)
提交的任务按先进先出(FIFO)出列,并发执行在不同的线程上。任何一个点上执行的任务数量是变化的,具体由系统条件决定,并且任务完成先后顺序也是任意的。串行队列(Serial queues)
串行队列一次只行一个任务,无论是同步(dispatch_sync)还是异步(dispatch_async)提交任务给串行队列,都遵循FIFO原则。
注意:提交到队列中的任务是串行还是并行执行,由队列本身决定。
获取队列的方式
- 创建队列
//创建串行队列
let serialQ = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL)
//创建并发队列
let concurrentQ = dispatch_queue_create("concurrentQ", DISPATCH_QUEUE_CONCURRENT)
- 获取主线程中的主队列(Main queue)
因为主线程只有一个,所有这自然是串行队列(Serial queues)。一切跟UI有关的操作必须放在主线程中执行。
let mainQ = dispatch_get_main_queue()
- 获取系统的全局队列
系统也提供了一些全局并发队列(The Global Concurrent Queues)。这些队列与它们自己的服务质量(Qos)类有关,服务质量(Qos)类向GCD提供了被提交任务的意图以便GCD更好的决定它们的执行顺序。
- QOS_CLASS_USER_INTERACTIVE: user interactive类代表那些为了提供良好的用户体验而必须立即被完成的任务。一般用于更新UI,事件处理和执行时间短的,工作量小的任务。
- QOS_CLASS_USER_INITIATED:user initiated类代表那些UI被触发引起并且能够异步执行(dispatch_async)的任务。一般用在当用户在等待立即返回结果的时候或者那些需要进一步用户交互的任务。
- QOS_CLASS_UTILITY:utility 类代表那些长时间执行的任务,例如用户可见的进度指示条。一般用在计算,I/O操作,网络等类似的任务。这个类被设计得很高效。
- QOS_CLASS_BACKGROUND:background类代表那些用户并不直接关注的任务。一般用来预取数据,维护和那些没有用户交互或者没有时间要求的任务。
值得注意的是,在获取全局队列的时候,dispatch_get_global_queue方法也可以指定优先权值。当指定优先权值时,这些权值会对应合适的Qos类:
import Foundation
//指定Qos类
let globalUserInteractiveQ = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0)
let globalUserInitiatedQ = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
let globalUtilityQ = dispatch_get_global_queue(QOS_CLASS_UTILITY, 0)
let globalBackgroundQ = dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0)
//指定优先权值
let priorityHighQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
let priorityDefaultQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let priorityLowQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)
let priorityBackgroundQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)
print(globalUserInteractiveQ.description)
print(globalUserInitiatedQ.description)
print(globalUtilityQ.description)
print(globalBackgroundQ .description)
print("-------------------------------------------------------------")
print(priorityHighQ.description)
print(priorityDefaultQ.description)
print(priorityLowQ.description)
print(priorityBackgroundQ.description)
运行程序,得到结果如下:
从运行结果我们不难看出:
DISPATCH_QUEUE_PRIORITY_HIGH = QOS_CLASS_USER_INITIATED
DISPATCH_QUEUE_PRIORITY_LOW = QOS_CLASS_UTILITY
DISPATCH_QUEUE_PRIORITY_BACKGROUND = DISPATCH_QUEUE_PRIORITY_BACKGROUND
至于default Qos,Apple官方做了如下描述:
The priority level of this QoS falls between user-initiated and utility. This QoS is not intended to be used by developers to classify work. Work that has no QoS information assigned is treated as default, and the GCD global queue runs at this level.
执行UI关联任务
UI关联的任务只能在主线程中执行,所以主队列(main queue)是唯一的选择。向主队列中提交任务的正确方法就是用dispatch_async方法。
注意:不能用dispatch_sync方法向main queue中提交任务,因为会造成主线程无限期的阻塞并使程序陷入死锁。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let mainQ = dispatch_get_main_queue()
dispatch_async(mainQ){
print("Current thread = \(NSThread.currentThread())")
print("Main thread = \(NSThread.mainThread())")
}
dispatch_async(mainQ){[weak self] in
let alertController = UIAlertController(title: "GCD", message: "GCD is amazing!",
preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: "OK",
style: .Default,
handler: nil))
self!.presentViewController(alertController, animated: true,
completion: nil)
}
}
}
运行程序会输出:
执行UI无关的任务
对于任何与UI无关的任务,可以使用全局并发队列(The Global Concurrent Queues)。全局并发队列允许同步或者异步提交任务。但是同步提交的任务并不意味着程序要等待同步代码块执行完成才能继续,而是指并发队列会等待当前的代码块执行完成才会继续执行队列里面的下一个代码块。
注意:当你将代码块放进并发队列时,程序不会等待队列执行代码块而是直接继续。这是因为并发队列是在其他线程里面执行代码而非主线程。(有一个例外:当用dispatch_sync 方法提交一个任务到并发队列或者串行队列时,ios可能的话,会在当前线程去执行任务,而这个当前线程很可能是主线程。)
下面例子来说明:
import UIKit
class ViewController: UIViewController {
func printFrom1To10(){
for counter in 0..<10{
print("Counter = \(counter) - Thread = \(NSThread.currentThread())")
}
}
override func viewDidLoad() {
super.viewDidLoad()
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_sync(queue, printFrom1To10)
dispatch_sync(queue, printFrom1To10)
}
}
运行程序,输出结果如下:
注意到我们将printFrom1To10方法以同步的方式提交了2次到全局并发队列,可是结果却出乎意料。可以看出任务在主线程中以串行的方式执行了。这就是因为dispatch_sync方法会优先使用当前的线程。Apple对此有如下说明:
GCD Reference: As an optimization, this function invokes the block on the current thread when possible.
下面我们看一个从网络上下载图片并且用UIImageView显示的例子:
- 异步(dispatch_async)方式向并发队列提交一个代码块。
- 在上面提交的代码块中,用同步(dispatch_sync)的方式再次向这个并发队列提交一个代码块,用来下载图片。在一个异步的代码块中用同步的方式去下载图片只会堵塞用同步方法提交任务的队列,对主线程并无影响。从主线程来看,整个操作其实还是异步的。我们关心的是在下载的时候不会阻塞主线程。
- 下载完成后,我们用同步的方式向主队列提交显示图片的任务。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let concurrentQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_async(concurrentQ) { [weak self] in
var image:UIImage?
print("1.异步提交任务线程:"+NSThread.currentThread().description)
dispatch_sync(concurrentQ) { _ in
print("2.下载图片线程:"+NSThread.currentThread().description)
let url = NSURL(string:"https://github.com/SmileMelody/images/raw/master/image1.png")
let urlRequest = NSURLRequest(URL: url!)
image = UIImage(data: try! NSURLConnection.sendSynchronousRequest(urlRequest, returningResponse: nil))
dispatch_sync(dispatch_get_main_queue()){ _ in
print("3.更新UI线程:"+NSThread.currentThread().description)
let imageView = UIImageView(frame: self!.view.bounds)
imageView.contentMode = .ScaleAspectFit
imageView.image = image
imageView.backgroundColor = UIColor.redColor()
self!.view.addSubview(imageView)
}
}
}
}
}
执行程序,结果如下:
很显然,图片下载完成之后,才开始执行更新UI。同时线程1和线程2是同一个线程,这是因为我们前面提到的dispatch_sync这个方法会优先使用当前线程。而当前线程就是线程1,因为下载图片的任务是在线程1中提交的。这么做的原因就是在图片完全下载后才更新UI,同时不阻塞主线程。
注意:这里的NSURLConnection.sendSynchronousRequest方法也是同步的方法。如果用异步的方法那么在图片还未下载完全之前,就可能执行更新UI的任务。
NSURLConnection.sendSynchronousRequest方法在IOS9里面已经被弃用,而实际上我们下载图片更新UI也不需要上面这么麻烦:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string:"https://github.com/SmileMelody/images/raw/master/image1.png")
let task = NSURLSession.sharedSession().dataTaskWithURL(url!) { data,response,error in
if data?.length > 0 && error == nil {
dispatch_async(dispatch_get_main_queue()){ [weak self] in
let imageView = UIImageView(frame: self!.view.bounds)
imageView.contentMode = .ScaleAspectFit
imageView.image = UIImage(data: data!)
imageView.backgroundColor = UIColor.redColor()
self!.view.addSubview(imageView)
}
}
}
task.resume()
}
}
延迟执行任务(dispatch_after)
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let delayInSeconds = 4.0
let delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC)))
let concurrentQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
dispatch_after(delayInNanoSeconds, concurrentQ){ _ in
/* Perform your operations here */
}
}
}
只执行一次(dispatch_once)
import Foundation
var token: dispatch_once_t = 0
var numberOfEntries = 0
func executedOnlyOnce(){
numberOfEntries++
print("Executed \(numberOfEntries) time(s)")
}
dispatch_once(&token, executedOnlyOnce)
dispatch_once(&token, executedOnlyOnce)
结果:
任务组合(dispatch_group)
dispatch_group_create:创建一个调度任务组
dispatch_group_async:异步提交任务到一个任务组
dispatch_group_notify:监听任务组中所有任务执行完毕后再执行,不阻塞当前线程
dispatch_group_wait:等待指定时间直到所有任务完成,阻塞当前线程
模拟提交三个任务:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let taskGroup = dispatch_group_create()
let concurrentQ = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)
dispatch_group_async(taskGroup, concurrentQ) { () -> Void in
print("task_1")
}
dispatch_group_async(taskGroup, concurrentQ) { () -> Void in
print("task_2")
}
dispatch_group_async(taskGroup, concurrentQ) { () -> Void in
print("task_3")
}
dispatch_group_notify(taskGroup, concurrentQ) { () -> Void in
print("all done")
}
}
}
输出为:
task_1
task_3
task_2
all done
dispatch_group_enter & dispatch_group_leave:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let taskGroup = dispatch_group_create()
let serialQ = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL)
let concurrentQ = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
for index in 1...3{
dispatch_group_enter(taskGroup)
downLoadImage(index, queue: serialQ)
dispatch_group_leave(taskGroup)
}
dispatch_group_enter(taskGroup)
dispatch_async(concurrentQ) { _ in
sleep(4)
print("俺是插队的任务。")
}
dispatch_group_leave(taskGroup)
dispatch_group_notify(taskGroup, serialQ) { () -> Void in
print("任务完成!")
}
}
func downLoadImage(num:Int,queue:dispatch_queue_t){
dispatch_async(queue) { _ in
sleep(2)
print("任务:"+"\(num)")
}
}
}
输出结果:
任务:1
俺是插队的任务。
任务:2
任务:3
任务完成!
指定次数提交同一个任务(dispatch_apply)
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let taskGroup = dispatch_group_create()
let serialQ = dispatch_queue_create("", DISPATCH_QUEUE_SERIAL)
let concurrentQ = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
dispatch_group_enter(taskGroup)
dispatch_async(concurrentQ) { _ in
sleep(4)
print("俺是插队的任务。")
}
dispatch_group_leave(taskGroup)
//指定次数提交相同任务到队列中
dispatch_apply(3, serialQ, downLoadImage)
//注意这里只是监听了serialQ里面的任务
dispatch_group_notify(taskGroup, serialQ) { () -> Void in
print("任务完成!")
}
}
func downLoadImage(var num:Int = 1){
sleep(2)
print("任务:"+"\(num++)")
}
}
输出为:
任务:0
任务:1
俺是插队的任务。
任务:2
任务完成!
信号量
dispatch_semaphore_create:用于创建信号量,可以指定初始化信号量的值
dispatch_semaphore_wait:判断信号量,如果为1,则往下执 行,信号量减1。如果是0,则等待
dispatch_semaphore_signal:代表运行结束,信号量加1
import UIKit
class ViewController: UIViewController {
let semaphore = dispatch_semaphore_create(2)
var count = 1
func doSomething(){
if dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) == 0{
sleep(3)
print("第一次"+"\(count++)"+"执行")
dispatch_semaphore_signal(semaphore)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let concurrentQ = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
for _ in 1...8{
dispatch_async(concurrentQ) { () -> Void in
self.doSomething()
}
}
}
}
可以清楚的看见每次只有两个线程执行了:
dispatch barriers
就如同它的名字一样,在队列执行的任务中增加“栅栏”,在增加“栅栏”之前已经开始执行的block将会继续执行,当dispatch_barrier_async开始执行的时候其他的block处于等待状态,dispatch_barrier_async的任务执行完后,其后的block才会执行。
import UIKit
class ViewController: UIViewController {
func writeToFile(flag:Int){
NSUserDefaults.standardUserDefaults().setInteger(flag, forKey: "barrier")
}
func readFromFile(){
print(NSUserDefaults.standardUserDefaults().integerForKey("barrier"))
}
override func viewDidLoad() {
super.viewDidLoad()
let concurrentQ = dispatch_queue_create("", DISPATCH_QUEUE_CONCURRENT)
writeToFile(1)
dispatch_async(concurrentQ){self.readFromFile()}
dispatch_async(concurrentQ){self.readFromFile()}
dispatch_async(concurrentQ){self.readFromFile()}
dispatch_barrier_async(concurrentQ){self.writeToFile(7);self.readFromFile()}
dispatch_async(concurrentQ){self.readFromFile()}
dispatch_async(concurrentQ){self.readFromFile()}
}
}