Grand Central Dispatch 最全入門手冊

Grand Central Dispatch 也就是我們常說的 GCD 是蘋果為多核心處理器開發(fā)的一套異步調(diào)度機(jī)制。蘋果官方文檔中是這樣介紹它的:

Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware in iOS and OS X.

這里比較重要的一點是它依托了語言特性,絕大部分是 Block 特性,使得欲執(zhí)行的函數(shù)體能很方便地作為參數(shù)傳遞。

很多剛接觸 iOS 開發(fā)的朋友可能會認(rèn)為 GCD 很復(fù)雜,事實上這套 API 十分簡潔易用,往往使用之后就不會再去用傳統(tǒng)的方式處理多線程問題了,熟練使用 GCD 能極大提升我們的開發(fā)效率。下面我將從功能的角度逐一講解它的使用方法。

<h2>注意,下面的這些函數(shù)參數(shù)都很明確,我就不一一列舉函數(shù)的參數(shù)是什么了。</h2>

Queue

GCD 的異步核心是列隊,系統(tǒng)會有自己的主列隊和全局列隊,任何在主列隊進(jìn)行的任務(wù)都會阻塞 UI 事件,我們只要了解這一點就可以了。

下面是獲取和創(chuàng)建列隊的函數(shù):

  • dispatch_get_main_queue: 獲取主列隊。
  • dispatch_get_global_queue: 獲取全局列隊,其中第一個參數(shù)為優(yōu)先級,可選的優(yōu)先級有以下幾個:
    • QOS_CLASS_USER_INTERACTIVE
    • QOS_CLASS_USER_INITIATED
    • QOS_CLASS_DEFAULT
    • QOS_CLASS_UTILITY
    • QOS_CLASS_BACKGROUND
      從上到下優(yōu)先級依次降低,該函數(shù)的第二個參數(shù)為系統(tǒng)保留,每次都傳入 0 即可。通常我們第一個參數(shù)也傳入 0 來獲取默認(rèn)的全局列隊。
  • dispatch_queue_create: 創(chuàng)建一個列隊,第一個參數(shù)為標(biāo)識符,通常用倒過來寫的域名 (eg. com.apple.myqueue) ,第二個參數(shù)為列隊類型,有下列類型可選:
    • DISPATCH_QUEUE_SERIAL
    • DISPATCH_QUEUE_CONCURRENT
      顧名思義,一個是串行的,一個是并行的。如果傳入 NULL則表示串行列隊。

有了列隊,我們就可以向其中分發(fā)任務(wù)了,下面是分發(fā)任務(wù)時需要的函數(shù):

  • dispatch_async: 將執(zhí)行體放入列隊然后立即返回,如果列隊是主列隊,那么就相當(dāng)于延后執(zhí)行代碼。
  • dispatch_sync: 將執(zhí)行體放入列隊并執(zhí)行,等待執(zhí)行完畢再返回。在主線程中,列隊堅決不能是主列隊,不然主線程被掛起來等待任務(wù)執(zhí)行結(jié)束,然而這個任務(wù)就是要在主線程執(zhí)行,那么這個任務(wù)永遠(yuǎn)不會執(zhí)行,主線程也永遠(yuǎn)不會被喚醒。同理列隊也不能是調(diào)用 dispatch_sync 的當(dāng)前列隊,同樣會產(chǎn)生死鎖。
  • dispatch_apply: 迭代執(zhí)行,第一個參數(shù)是迭代次數(shù),第二個參數(shù)是目標(biāo)列隊,第三個參數(shù)是欲執(zhí)行的 block,block 的參數(shù)就是當(dāng)前迭代的下標(biāo)。注意,這個函數(shù)將會等待所有執(zhí)行體完畢后才返回。適當(dāng)情況下可以用這個函數(shù)來代替 for-loop 以提高性能。

通常,我們在 Global Queue 中執(zhí)行一些耗時操作,然后在 Main Queue 中將結(jié)果更新到 UI。

Time

GCD 有幾個比較重要的函數(shù)與時間相關(guān):

  • dispatch_time: 創(chuàng)建一個時間對象,第一個參數(shù)是參考時間,第二個參數(shù)是時間增量,那么最終返回的時間就是 (參考時間 + 增量),通常使用方法有下面幾種:
    • dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_MSEC * 100)): 當(dāng)前時間后 100 毫秒
    • dispatch_time(DISPATCH_TIME_NOW, Int64(NSEC_PER_SEC * 3)): 當(dāng)前時間后 3 秒
      其中 NSEC_PER_MSECNSEC_PER_SEC 是時間計量單位。
  • dispatch_after: 在指定時間后執(zhí)行,這個函數(shù)將立即返回,等到指定時間后才會在指定的列隊中執(zhí)行指定任務(wù)。

Group

如果需要批量執(zhí)行一批任務(wù),并且想在所有這批任務(wù)結(jié)束后得到通知,這時就需要用到 Group 了。

  • dispatch_group_create: 創(chuàng)建一個組,無任何參數(shù)。
  • dispatch_group_async: 同 dispatch_async ,只不過第一個參數(shù)前多了一個 group ,即將任務(wù)歸為這一組。
  • dispatch_group_enter: 增加組的計數(shù),此時你不需要提交執(zhí)行體,等待 dispatch_group_leave 被調(diào)用后則表示一個任務(wù)完成,可嵌套,總之保證兩者調(diào)用次數(shù)平衡即可。下面是一個例子:
let group = dispatch_group_create()

dispatch_group_enter(group)
dispatch_group_enter(group)
dispatch_group_leave(group)
dispatch_group_leave(group)
  • dispatch_group_notify: 在該組任務(wù)都完成后在指定列隊中調(diào)用一個回調(diào)函數(shù)。
  • dispatch_group_wait: 阻塞線程直到該組任務(wù)都完成,或指定時間超時后返回。

Barrier

這個詞,有意思。


看翻譯感覺很難理解,所以我用通俗的語言表述一下。通常我們會遇到這樣一個問題:一個線程需要訪問資源 A,而另外一個線程需要修改資源 B,這時就會有競態(tài)問題。我們堅決不能讓這個情況出現(xiàn),這里我們就可以用 Barrier 的這個函數(shù):

dispatch_barrier_async

執(zhí)行這個函數(shù)后,執(zhí)行體將會被放到列隊中并等待,直到整個列隊中沒有在執(zhí)行的任務(wù),這時這個執(zhí)行體才會被執(zhí)行,并且在它完成之前不會有其他任務(wù)會同時進(jìn)行。簡單說就是這個執(zhí)行體必須自己一個人執(zhí)行,不得有人和它一同執(zhí)行。

說起來還是好抽象,上代碼:

import Foundation

class Counter {
    
    var counter: Int = 0
    let queue = dispatch_queue_create("com.example.barrierqueue", nil)
    
    func get() -> Int {
        var result: Int!
        dispatch_sync(queue) { 
            result = self.counter
        }
        return result
    }
    
    func enter() {
        adjustBy(1)
    }
    
    func leave() {
        adjustBy(-1)
    }
    
    private func adjustBy(by: Int) {
        dispatch_barrier_async(queue) { 
            self.counter += by
        }
    }
    
}

我們可以看到,訪問一個變量可以是同時進(jìn)行的,因為他們不會修改這個變量。然而進(jìn)行修改時必須一個一個來,這里我們就可以用 barrier 擋一下,等修改完了再干別的。

Semaphore

信號量是一個控制資源訪問數(shù)量的方法,例如一個資源只能被 5 個人同時訪問,那么我們就可以用信號量記錄訪問數(shù),并阻擋超出數(shù)量的人進(jìn)行的訪問。主要函數(shù)有下面幾個:

  • dispatch_semaphore_create: 傳入一個整型作為最大訪問數(shù)量,創(chuàng)建并返回這個信號量類型。
  • dispatch_semaphore_wait: 使用資源前調(diào)用它,它會減少計數(shù)器,如果計數(shù)器小于零,這個函數(shù)將會阻塞。
  • dispatch_semaphore_signal: 使用資源后調(diào)用它,它會增加計數(shù)器,如果計數(shù)器大于等于零,那些等待使用資源的線程會被喚醒。

其實很多情況我們都用信號量來將一些無法修改的異步函數(shù)變成同步函數(shù),下面是一個例子:

func fetchURLSync(URL: NSURL) -> NSData? {
    let sem = dispatch_semaphore_create(0)
    
    var _data: NSData?
    let task = NSURLSession.sharedSession().dataTaskWithURL(URL) { (data, response, error) in
        _data = data
        
        dispatch_semaphore_signal(sem)
    }
    
    task.resume()
    
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER)
    
    return _data
}

Source

用過 CFRunloop / NSRunloop 的朋友應(yīng)該不陌生這個詞,GCD也有 Source 的概念,怎么理解呢,Source 就像一個信號發(fā)射器,有信號發(fā)出就會有響應(yīng)的處理。比較常見的信號類型有 Timer、文件讀寫、進(jìn)程UNIX Signal、Mach Port、內(nèi)存壓力。但是在 iOS 開發(fā)中,我們也就是用用 Timer 了,下面直接上一個例子:

func createAndStartTimer(interval: UInt64, leeway: UInt64, queue: dispatch_queue_t, block: () -> Void) -> dispatch_source_t? {
    let timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
    
    if timer != nil {
        dispatch_source_set_timer(timer, dispatch_walltime(nil, 0), interval, leeway)
        dispatch_source_set_event_handler(timer, block)
        dispatch_resume(timer)
    }
    
    return timer
}

func stopTimer(timer: dispatch_source_t) {
    dispatch_source_cancel(timer)
}

上面代碼拿來用就好,leeway 是精準(zhǔn)度的意思,精準(zhǔn)度越高資源消耗越大。

洋洋灑灑寫了這么多,應(yīng)該算覆蓋得比較全了,就醬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 3.1 Grand Central Dispatch(GCD)概要 3.1.1 什么是CGD Grand Cent...
    SkyMing一C閱讀 1,784評論 0 22
  • 同步/異步 同步:多個任務(wù)情況下,一個任務(wù)A執(zhí)行結(jié)束,才可以執(zhí)行另一個任務(wù)B。只存在一個線程也就是主線程。 異步:...
    XLsn0w閱讀 336評論 0 0
  • 我們知道在iOS開發(fā)中,一共有四種多線程技術(shù):pthread,NSThread,GCD,NSOperation: ...
    請叫我周小帥閱讀 1,561評論 0 1
  • 背景 擔(dān)心了兩周的我終于輪到去醫(yī)院做胃鏡檢查了!去的時候我都想好了最壞的可能(胃癌),之前在網(wǎng)上查的癥狀都很相似。...
    Dely閱讀 9,402評論 21 42
  • 世間好物不堅牢,彩云易散琉璃脆。 某天,看著楊絳先生的《我們仨》,轉(zhuǎn)過頭去看著先生,冒出來一句:我好羨慕楊先生這樣...
    桃酥1618閱讀 582評論 8 5

友情鏈接更多精彩內(nèi)容