iOS GCD的常見API使用指南

來源:互聯網
上載者:User

標籤:

iOS GCD使用指南

 

Grand Central Dispatch(GCD)是非同步執行任務的技術之一。一般將應用程式中記述的線程管理用的代碼在系統級中實現。開發人員只需要定義想執行的任務並追加到適當的Dispatch Queue中,GCD就能產生必要的線程並計劃執行任務。由於線程管理是作為系統的一部分來實現的,因此可統一管理,也可執行任務,這樣就比以前的線程更有效率。

 

Dispatch Queue

Dispatch Queue是用來執行任務的隊列,是GCD中最基本的元素之一。

Dispatch Queue分為兩種:

 

  • Serial Dispatch Queue,按添加進隊列的順序(先進先出)一個接一個的執行
  • Concurrent Dispatch Queue,並發執行隊列裡的任務
簡而言之,Serial Dispatch Queue只使用了一個線程,Concurrent Dispatch Queue使用了多個線程(具體使用了多少個,由系統決定)。 可以通過兩種方式來獲得Dispatch Queue,第一種方式是自己建立一個:

let myQueue: dispatch_queue_t = dispatch_queue_create("com.xxx", nil) 

第一個參數是隊列的名稱,一般是使用倒序的全網域名稱。雖然可以不給隊列指定一個名稱,但是有名稱的隊列可以讓我們在遇到問題時更好調試;當第二個參數為nil時返回Serial Dispatch Queue,如上面那個例子,當指定為DISPATCH_QUEUE_CONCURRENT時返回Concurrent Dispatch Queue。

需要注意一點,如果是在OS X 10.8或iOS 6以及之後版本中使用,Dispatch Queue將會由ARC自動管理,如果是在此之前的版本,需要自己手動釋放,如下: 

 

let myQueue: dispatch_queue_t = dispatch_queue_create("com.xxx", nil)

dispatch_async(myQueue, { () -> Void in

    println("in Block")

})

dispatch_release(myQueue) 

 

以上是通過手動建立的方式來擷取Dispatch Queue,第二種方式是直接擷取系統提供的Dispatch Queue。

要擷取的Dispatch Queue無非就是兩種類型:

 

  • Main Dispatch Queue
  • Global Dispatch Queue / Concurrent Dispatch Queue
一般只在需要更新UI時我們才擷取Main Dispatch Queue,其他情況下用Global Dispatch Queue就滿足需求了:

//擷取Main Dispatch Queue

let mainQueue = dispatch_get_main_queue()

//擷取Global Dispatch Queue

let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

得到的Global Dispatch Queue實際上是一個Concurrent Dispatch Queue,Main Dispatch Queue實際上就是Serial Dispatch Queue(並且只有一個)。擷取Global Dispatch Queue的時候可以指定優先順序,可以根據自己的實際情況來決定使用哪種優先順序。一般情況下,我們通過第二種方式擷取Dispatch Queue就行了。 dispatch_after

dispatch_after能讓我們添加進隊列的任務延時執行,比如想讓一個Block在10秒後執行: 

 

var time = dispatch_time(DISPATCH_TIME_NOW, (Int64)(10 * NSEC_PER_SEC))

dispatch_after(time, globalQueue) { () -> Void in

    println("在10秒後執行")

 

NSEC_PER_SEC表示的是秒數,它還提供了NSEC_PER_MSEC表示毫秒。

上面這句dispatch_after的真正含義是在10秒後把任務添加進隊列中,並不是表示在10秒後執行,大部分情況該函數能達到我們的預期,只有在對時間要求非常精準的情況下才可能會出現問題。

擷取一個dispatch_time_t類型的值可以通過兩種方式來擷取,以上是第一種方式,即通過dispatch_time函數,另一種是通過dispatch_walltime函數來擷取,dispatch_walltime需要使用一個timespec的結構體來得到dispatch_time_t。通常dispatch_time用於計算相對時間,dispatch_walltime用於計算絕對時間,我寫了一個把NSDate轉成dispatch_time_t的Swift方法: 

 

func getDispatchTimeByDate(date: NSDate) -> dispatch_time_t {

    let interval = date.timeIntervalSince1970

    var second = 0.0

    let subsecond = modf(interval, &second)

    var time = timespec(tv_sec: __darwin_time_t(second), tv_nsec: (Int)(subsecond * (Double)(NSEC_PER_SEC)))

    return dispatch_walltime(&time, 0)

這個方法接收一個NSDate對象,然後把NSDate轉成dispatch_walltime需要的timespec結構體,最後再把dispatch_time_t返回,同樣是在10秒後執行,之前的代碼在調用部分需要修改成: 

var time = getDispatchTimeByDate(NSDate(timeIntervalSinceNow: 10))

dispatch_after(time, globalQueue) { () -> Void in

    println("在10秒後執行")

}

這就是通過絕對時間來使用dispatch_after的例子。

 

dispatch_group可能經常會有這樣一種情況:我們現在有3個Block要執行,我們不在乎它們執行的順序,我們只希望在這3個Block執行完之後再執行某個操作。這個時候就需要使用dispatch_group了:

let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

let group = dispatch_group_create()

 

dispatch_group_async(group, globalQueue) { () -> Void in

    println("1")

}

dispatch_group_async(group, globalQueue) { () -> Void in

    println("2")

}

dispatch_group_async(group, globalQueue) { () -> Void in

    println("3")

}

dispatch_group_notify(group, globalQueue) { () -> Void in

    println("completed")

}

輸出的順序與添加進隊列的順序無關,因為隊列是Concurrent Dispatch Queue,但“completed”的輸出一定是在最後的:
312completed
除了使用dispatch_group_notify函數可以得到最後執行完的通知外,還可以使用

let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

let group = dispatch_group_create()

 

dispatch_group_async(group, globalQueue) { () -> Void in

    println("1")

}

dispatch_group_async(group, globalQueue) { () -> Void in

    println("2")

}

dispatch_group_async(group, globalQueue) { () -> Void in

    println("3")

}

//使用dispatch_group_wait函數

dispatch_group_wait(group, DISPATCH_TIME_FOREVER)

println("completed")

需要注意的是,dispatch_group_wait實際上會使當前的線程處於等待的狀態,也就是說如果是在主線程執行dispatch_group_wait,在上面的Block執行完之前,主線程會處於卡死的狀態。可以注意到dispatch_group_wait的第二個參數是指定逾時的時間,如果指定為DISPATCH_TIME_FOREVER(如上面這個例子)則表示會永久等待,直到上面的Block全部執行完,除此之外,還可以指定為具體的等待時間,根據dispatch_group_wait的傳回值來判斷是上面block執行完了還是等待逾時了。最後,同之前建立dispatch_queue一樣,如果是在 OS X 10.8或iOS 6以及之後版本中使用,Dispatch Group將會由ARC自動管理,如果是在此之前的版本,需要自己手動釋放。 dispatch_barrier_async

dispatch_barrier_async就如同它的名字一樣,在隊列執行的任務中增加“柵欄”,在增加“柵欄”之前已經開始執行的block將會繼續執行,當dispatch_barrier_async開始執行的時候其他的block處於等待狀態,dispatch_barrier_async的任務執行完後,其後的block才會執行。我們簡單的寫個例子,假設這個例子有讀檔案和寫檔案的部分:

 

func writeFile() {

    NSUserDefaults.standardUserDefaults().setInteger(7, forKey: "Integer_Key")

}

 

func readFile(){

    print(NSUserDefaults.standardUserDefaults().integerForKey("Integer_Key"))

寫檔案只是在NSUserDefaults寫入一個數字7,讀只是將這個數字列印出來而已。我們要避免在寫檔案時候正好有線程來讀取,就使用dispatch_barrier_async函數: 

 

 

NSUserDefaults.standardUserDefaults().setInteger(9, forKey: "Integer_Key")

let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

dispatch_async(globalQueue) {self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

dispatch_barrier_async(globalQueue) {self.writeFile() ; self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

dispatch_async(globalQueue) {self.readFile()}

dispatch_async(globalQueue) {self.readFile()} 

我們先將一個9初始化到NSUserDefaults的Integer_Key中,然後在中間執行dispatch_barrier_async函數,由於這個隊列是一個Concurrent Dispatch Queue,能同時並發多少線程是由系統決定的,如果添加dispatch_barrier_async的時候,其他的block(包括上面4個block)還沒有開始執行,那麼會先執行dispatch_barrier_async裡的任務,其他block全部處於等待狀態。如果添加dispatch_barrier_async的時候,已經有block在執行了,那麼dispatch_barrier_async會等這些block執行完後再執行。

 

 

dispatch_applydispatch_apply會將一個指定的block執行指定的次數。如果要對某個數組中的所有元素執行同樣的block的時候,這個函數就顯得很有用了,用法很簡單,指定執行的次數以及Dispatch Queue,在block回調中會帶一個索引,然後就可以根據這個索引來判斷當前是對哪個元素進行操作: 

let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

dispatch_apply(10, globalQueue) { (index) -> Void in

    print(index)

}

print("completed") 

由於是Concurrent Dispatch Queue,不能保證哪個索引的元素是先執行的,但是“completed”一定是在最後列印,因為dispatch_apply函數是同步的,執行過程中會使線程在此處等待,所以一般的,我們應該在一個非同步線程裡使用dispatch_apply函數:

let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

dispatch_async(globalQueue, { () -> Void in

    dispatch_apply(10, globalQueue) { (index) -> Void in

        print(index)

    }

    print("completed")

})

print("在dispatch_apply之前") 

dispatch_suspend / dispatch_resume某些情況下,我們可能會想讓Dispatch Queue暫時停止一下,然後在某個時刻恢複處理,這時就可以使用dispatch_suspend以及dispatch_resume函數: 

//暫停

dispatch_suspend(globalQueue)

//恢複

dispatch_resume(globalQueue)

暫停時,如果已經有block正在執行,那麼不會對該block的執行產生影響。dispatch_suspend只會對還未開始執行的block產生影響。 Dispatch Semaphore訊號量在多線程開發中被廣泛使用,當一個線程在進入一段關鍵代碼之前,線程必須擷取一個訊號量,一旦該關鍵程式碼片段完成了,那麼該線程必須釋放訊號量。其它想進入該關鍵程式碼片段的線程必須等待前面的線程釋放訊號量。訊號量的具體做法是:當訊號計數大於0時,每條進來的線程使計數減1,直到變為0,變為0後其他的線程將進不來,處於等待狀態;執行完任務的線程釋放訊號,使計數加1,如此迴圈下去。下面這個例子中使用了10條線程,但是同時只執行一條,其他的線程處於等待狀態:

let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

let semaphore =  dispatch_semaphore_create(1)

for i in 0 ... 9 {

    dispatch_async(globalQueue, { () -> Void in

        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

        let time = dispatch_time(DISPATCH_TIME_NOW, (Int64)(2 * NSEC_PER_SEC))

        dispatch_after(time, globalQueue) { () -> Void in

            print("2秒後執行")

            dispatch_semaphore_signal(semaphore)

        }

    })

}

取得訊號量的線程在2秒後釋放了資訊量,相當於是每2秒執行一次。通過上面的例子可以看到,在GCD中,用dispatch_semaphore_create函數能初始化一個訊號量,同時需要指定訊號量的初始值;使用dispatch_semaphore_wait函數分配訊號量並使計數減1,為0時處於等待狀態;使用dispatch_semaphore_signal函數釋放訊號量,並使計數加1。另外dispatch_semaphore_wait同樣也支援逾時,只需要給其第二個參數指定逾時的時候即可,同Dispatch Group的dispatch_group_wait函數類似,可以通過傳回值來判斷。這個函數也需要注意,如果是在 OS X 10.8或iOS 6以及之後版本中使用,Dispatch Semaphore將會由ARC自動管理,如果是在此之前的版本,需要自己手動釋放。  dispatch_oncedispatch_once函數通常用在單例模式上,它可以保證在程式運行期間某段代碼只執行一次,如果我們要通過dispatch_once建立一個單例類,在Swift可以這樣:

class SingletonObject {

    class var sharedInstance : SingletonObject {

        struct Static {

            static var onceToken : dispatch_once_t = 0

            static var instance : SingletonObject? = nil

        }

        dispatch_once(&Static.onceToken) {

            Static.instance = SingletonObject()

        }

        return Static.instance!

    }

}

這樣就能通過GCD的安全機制保證這段代碼只執行一次。  

傳送門:CSDN iOS 論壇,如果有什麼問題,我會儘力解答,還望共同學習


如需轉載,請註明出處,謝謝!

 

iOS GCD的常見API使用指南

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.