調度隊列(Dispatch Queues)
GCD提供了調度隊列(Dispatch Queues)來處理被提交的任務,這些隊列負責管理你提交給GCD的任務并且按照先進先出(FIFO)的順序執(zhí)行它們官扣。調度隊列有以下3種:
主隊列(Main queue)
提交的任務執(zhí)行在主線程(main thread)延塑,串行(Serial)隊列,用來更新UI幻碱。并行隊列(Concurrent Queues)
提交的任務按先進先出(FIFO)出列绎狭,并發(fā)執(zhí)行在不同的線程上。任何一個點上執(zhí)行的任務數(shù)量是變化的褥傍,具體由系統(tǒng)條件決定儡嘶,并且任務完成先后順序也是任意的。串行隊列(Serial queues)
串行隊列一次只行一個任務恍风,無論是同步(dispatch_sync)還是異步(dispatch_async)提交任務給串行隊列蹦狂,都遵循FIFO原則。
注意:提交到隊列中的任務是串行還是并行執(zhí)行朋贬,由隊列本身決定凯楔。
獲取隊列的方式
- 創(chuàng)建隊列
//創(chuàng)建串行隊列
let serialQ = dispatch_queue_create("serialQ", DISPATCH_QUEUE_SERIAL)
//創(chuàng)建并發(fā)隊列
let concurrentQ = dispatch_queue_create("concurrentQ", DISPATCH_QUEUE_CONCURRENT)
- 獲取主線程中的主隊列(Main queue)
因為主線程只有一個,所有這自然是串行隊列(Serial queues)兄世。一切跟UI有關的操作必須放在主線程中執(zhí)行啼辣。
let mainQ = dispatch_get_main_queue()
- 獲取系統(tǒng)的全局隊列
系統(tǒng)也提供了一些全局并發(fā)隊列(The Global Concurrent Queues)。這些隊列與它們自己的服務質量(Qos)類有關御滩,服務質量(Qos)類向GCD提供了被提交任務的意圖以便GCD更好的決定它們的執(zhí)行順序鸥拧。
- QOS_CLASS_USER_INTERACTIVE: user interactive類代表那些為了提供良好的用戶體驗而必須立即被完成的任務党远。一般用于更新UI,事件處理和執(zhí)行時間短的富弦,工作量小的任務沟娱。
- QOS_CLASS_USER_INITIATED:user initiated類代表那些UI被觸發(fā)引起并且能夠異步執(zhí)行(dispatch_async)的任務。一般用在當用戶在等待立即返回結果的時候或者那些需要進一步用戶交互的任務腕柜。
- QOS_CLASS_UTILITY:utility 類代表那些長時間執(zhí)行的任務济似,例如用戶可見的進度指示條。一般用在計算盏缤,I/O操作砰蠢,網(wǎng)絡等類似的任務。這個類被設計得很高效唉铜。
- QOS_CLASS_BACKGROUND:background類代表那些用戶并不直接關注的任務台舱。一般用來預取數(shù)據(jù),維護和那些沒有用戶交互或者沒有時間要求的任務潭流。
值得注意的是竞惋,在獲取全局隊列的時候,dispatch_get_global_queue方法也可以指定優(yōu)先權值灰嫉。當指定優(yōu)先權值時拆宛,這些權值會對應合適的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)
//指定優(yōu)先權值
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.
執(zhí)行UI關聯(lián)任務
UI關聯(lián)的任務只能在主線程中執(zhí)行浑厚,所以主隊列(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)
}
}
}
運行程序會輸出:
執(zhí)行UI無關的任務
對于任何與UI無關的任務郑象,可以使用全局并發(fā)隊列(The Global Concurrent Queues)贡这。全局并發(fā)隊列允許同步或者異步提交任務。但是同步提交的任務并不意味著程序要等待同步代碼塊執(zhí)行完成才能繼續(xù)厂榛,而是指并發(fā)隊列會等待當前的代碼塊執(zhí)行完成才會繼續(xù)執(zhí)行隊列里面的下一個代碼塊盖矫。
注意:當你將代碼塊放進并發(fā)隊列時,程序不會等待隊列執(zhí)行代碼塊而是直接繼續(xù)击奶。這是因為并發(fā)隊列是在其他線程里面執(zhí)行代碼而非主線程辈双。(有一個例外:當用dispatch_sync 方法提交一個任務到并發(fā)隊列或者串行隊列時,ios可能的話柜砾,會在當前線程去執(zhí)行任務湃望,而這個當前線程很可能是主線程。)
下面例子來說明:
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次到全局并發(fā)隊列证芭,可是結果卻出乎意料瞳浦。可以看出任務在主線程中以串行的方式執(zhí)行了废士。這就是因為dispatch_sync方法會優(yōu)先使用當前的線程叫潦。Apple對此有如下說明:
GCD Reference: As an optimization, this function invokes the block on the current thread when possible.
下面我們看一個從網(wǎng)絡上下載圖片并且用UIImageView顯示的例子:
- 異步(dispatch_async)方式向并發(fā)隊列提交一個代碼塊。
- 在上面提交的代碼塊中官硝,用同步(dispatch_sync)的方式再次向這個并發(fā)隊列提交一個代碼塊矗蕊,用來下載圖片。在一個異步的代碼塊中用同步的方式去下載圖片只會堵塞用同步方法提交任務的隊列氢架,對主線程并無影響傻咖。從主線程來看,整個操作其實還是異步的达箍。我們關心的是在下載的時候不會阻塞主線程没龙。
- 下載完成后,我們用同步的方式向主隊列提交顯示圖片的任務缎玫。
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)
}
}
}
}
}
執(zhí)行程序,結果如下:
很顯然解滓,圖片下載完成之后赃磨,才開始執(zhí)行更新UI猪瞬。同時線程1和線程2是同一個線程勋拟,這是因為我們前面提到的dispatch_sync這個方法會優(yōu)先使用當前線程卵佛。而當前線程就是線程1悔橄,因為下載圖片的任務是在線程1中提交的卓舵。這么做的原因就是在圖片完全下載后才更新UI屑咳,同時不阻塞主線程害驹。
注意:這里的NSURLConnection.sendSynchronousRequest方法也是同步的方法而柑。如果用異步的方法那么在圖片還未下載完全之前移国,就可能執(zhí)行更新UI的任務吱瘩。
NSURLConnection.sendSynchronousRequest方法在IOS9里面已經(jīng)被棄用,而實際上我們下載圖片更新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()
}
}
延遲執(zhí)行任務(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 */
}
}
}
只執(zhí)行一次(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:創(chuàng)建一個調度任務組
dispatch_group_async:異步提交任務到一個任務組
dispatch_group_notify:監(jiān)聽任務組中所有任務執(zhí)行完畢后再執(zhí)行迹缀,不阻塞當前線程
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
任務完成!
指定次數(shù)提交同一個任務(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)
//指定次數(shù)提交相同任務到隊列中
dispatch_apply(3, serialQ, downLoadImage)
//注意這里只是監(jiān)聽了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:用于創(chuàng)建信號量祟剔,可以指定初始化信號量的值
dispatch_semaphore_wait:判斷信號量傅事,如果為1,則往下執(zhí) 行,信號量減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++)"+"執(zhí)行")
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()
}
}
}
}
可以清楚的看見每次只有兩個線程執(zhí)行了:
dispatch barriers
就如同它的名字一樣教届,在隊列執(zhí)行的任務中增加“柵欄”响鹃,在增加“柵欄”之前已經(jīng)開始執(zhí)行的block將會繼續(xù)執(zhí)行,當dispatch_barrier_async開始執(zhí)行的時候其他的block處于等待狀態(tài)案训,dispatch_barrier_async的任務執(zhí)行完后买置,其后的block才會執(zhí)行。
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()}
}
}