GCD for Swift

調度隊列(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í)行朋贬,由隊列本身決定凯楔。


獲取隊列的方式

  1. 創(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)
  1. 獲取主線程中的主隊列(Main queue)
    因為主線程只有一個,所有這自然是串行隊列(Serial queues)兄世。一切跟UI有關的操作必須放在主線程中執(zhí)行啼辣。
let mainQ = dispatch_get_main_queue()
  1. 獲取系統(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_INITIATEDuser initiated類代表那些UI被觸發(fā)引起并且能夠異步執(zhí)行(dispatch_async)的任務。一般用在當用戶在等待立即返回結果的時候或者那些需要進一步用戶交互的任務腕柜。
  • QOS_CLASS_UTILITYutility 類代表那些長時間執(zhí)行的任務济似,例如用戶可見的進度指示條。一般用在計算盏缤,I/O操作砰蠢,網(wǎng)絡等類似的任務。這個類被設計得很高效唉铜。
  • QOS_CLASS_BACKGROUNDbackground類代表那些用戶并不直接關注的任務台舱。一般用來預取數(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顯示的例子

  1. 異步(dispatch_async)方式向并發(fā)隊列提交一個代碼塊。
  2. 在上面提交的代碼塊中官硝,用同步(dispatch_sync)的方式再次向這個并發(fā)隊列提交一個代碼塊矗蕊,用來下載圖片。在一個異步的代碼塊中用同步的方式去下載圖片只會堵塞用同步方法提交任務的隊列氢架,對主線程并無影響傻咖。從主線程來看,整個操作其實還是異步的达箍。我們關心的是在下載的時候不會阻塞主線程没龙。
  3. 下載完成后,我們用同步的方式向主隊列提交顯示圖片的任務缎玫。
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()}
    }
    
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末强霎,一起剝皮案震驚了整個濱河市忿项,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌城舞,老刑警劉巖轩触,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異家夺,居然都是意外死亡脱柱,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門拉馋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來榨为,“玉大人,你說我怎么就攤上這事煌茴∷婀耄” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵蔓腐,是天一觀的道長矩乐。 經(jīng)常有香客問我,道長合住,這世上最難降的妖魔是什么绰精? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮透葛,結果婚禮上笨使,老公的妹妹穿的比我還像新娘。我一直安慰自己僚害,他們只是感情好硫椰,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布繁调。 她就那樣靜靜地躺著,像睡著了一般靶草。 火紅的嫁衣襯著肌膚如雪蹄胰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天奕翔,我揣著相機與錄音裕寨,去河邊找鬼。 笑死派继,一個胖子當著我的面吹牛宾袜,可吹牛的內容都是我干的。 我是一名探鬼主播驾窟,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼庆猫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绅络?” 一聲冷哼從身側響起月培,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎恩急,沒想到半個月后杉畜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡假栓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年寻行,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匾荆。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖杆烁,靈堂內的尸體忽然破棺而出牙丽,到底是詐尸還是另有隱情,我是刑警寧澤兔魂,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布烤芦,位于F島的核電站,受9級特大地震影響析校,放射性物質發(fā)生泄漏构罗。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一智玻、第九天 我趴在偏房一處隱蔽的房頂上張望遂唧。 院中可真熱鬧,春花似錦吊奢、人聲如沸盖彭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽召边。三九已至铺呵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間隧熙,已是汗流浹背片挂。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留贞盯,地道東北人音念。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像邻悬,于是被迫代替她去往敵國和親症昏。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

推薦閱讀更多精彩內容