iOS開發之再探多線程編程:Grand Central Dispatch詳解,多線程編程dispatch

來源:互聯網
上載者:User

iOS開發之再探多線程編程:Grand Central Dispatch詳解,多線程編程dispatch

之前關於iOS開發多線程的內容發布過一篇部落格,其中介紹了NSThread、操作隊列以及GCD,介紹的不夠深入。今天就以GCD為主題來全面的總結一下GCD的使用方式。GCD的曆史以及好處在此就不做過多的贅述了。本篇部落格會通過一系列的執行個體來好好的總結一下GCD。GCD在iOS開發中還是比較重要的,使用情境也是非常多的,處理一些比較耗時的任務時基本上都會使用到GCD, 在使用是我們也要主要一些安全執行緒也死結的東西。

本篇部落格中對iOS中的GCD技術進行了較為全面的總結,下方模擬器的就是我們今天要介紹的內容,都是關於GCD的。下方視圖控制器中每點擊一個Button都會使用GCD的相關技術來執行不同的內容。本篇部落格會對使用到的每個技術點進行詳細的講解。在講解時,為了易於理解,我們還會給出原理圖,這些原理圖都是根據本篇部落格中的執行個體進行創作的,在其他地方可見不著。

  

上面每個按鈕都對應著一坨坨的代碼,上面這個算是我們本篇部落格的一個,下面我們將會對每坨代碼進行詳細的介紹。通過這些介紹,你應該對GCD有了更全面而且更詳細的瞭解。建議參考著下方的介紹,然後自己動手去便實現代碼,這樣效果是灰常的好的。本篇部落格中的所有代碼都會在github上進行分享,本篇部落格的後方會給出github分享地址。其實本篇部落格可以作為你的GCD參考手冊,雖然本篇部落格沒有囊括所有GCD的東西,但是平時經常使用的部分還是有的。廢話少說,進入今天部落格的主題,接下來我們將一個Button接著一個Button的介紹。

 

一、常用GCD方法的封裝

為了便於執行個體的實現,我們首先對一些常用的GCD方法進行封裝和提取。該部分算是為下方的具體執行個體做準備的,本部分封裝了一些下面樣本所公用的方法。接下來我們將逐步的對每個提取的函數進行介紹,為下方樣本的實現做準備。在封裝方法之前,要說明一點的是在GCD中我們的任務是放在隊列中在不同的線程中執行的,要明白一點就是我們的任務是放在隊列中的Block中,然後Block再在相應的線程中完成我們的任務。

如所示,在下方隊列中存入了三個Block,每個Block對應著一個任務,這些任務會根據隊列的特性已經執行方式被放到相應的線程中來執行。隊列可分為並行隊列(Concurrent Qeueu)和串列隊列(Serial Queue),隊列可以進行同步執行(Synchronize)以及非同步執行(Asynchronize), 稍後會進行詳細的分析與介紹。我們要知道隊列第GCD的基礎。

  

 

1.擷取當前線程與當前線程休眠

首先我們將擷取當前線程的方法進行封裝,因為有時候我們會經常查看我們的任務是在那些線程中執行的。在此我們使用了NSThread的currentThread()方法來擷取當前線程。下方的getCurrentThread()方法就是我們提取的擷取當前線程的方法。方法內容比較簡單,在此就不做過多贅述了。

  

上述程式碼片段是擷取當前線程的方法,接著我們要實現一個讓當前線程休眠的方法。因為我們在樣本時,常常會讓當前線程來休眠一段時間來類比那些耗時的操作。下方程式碼片段中的currentThreadSleep()函數就是我們提取的當前線程休眠的函數。該函數有一個NSTimeInterval類型的參數,該參數就是要休眠的時間。NSTimeInterval其實就是Double類型的別名,所以我們在調用currentThreadSleep()方法時需要傳入一個Double類型的休眠時間。當然你也可以調用sleep()方法來對當前線程進行休眠,但是需要注意的是sleep()的參數是UInt32位的整型。下方就是我們休眠當前線程的函數。

  

 

2.擷取主隊列與全域隊列

下方封裝的getMainQueue()函數就是擷取主隊列的函數,因為有時候我們在其他線程中處理完耗時的任務(比如網路請求)後,需要在主隊列中對UI進行更新。因為我們知道在iOS中有個RunLoop的概念,在iOS系統中觸摸事件、螢幕重新整理等都是在RunLoop中做的。因為本篇部落格的主題是GCD, 在此就對RunLoop做過多的贅述了,如果你對RunLoop不太瞭解,那麼你就先簡單將RunLoop理解成1/60執行一次的迴圈即可,當然真正的RunLoop要比一個單純的迴圈複雜的多,以後有機會的話在以RunLoop為主題更新一篇部落格吧。言歸正傳,下方就是擷取我們主隊列的方法,簡單一點的說因為我們要更新UI,所以要擷取主隊列。

  

接下來我們要封裝一個擷取全域隊列(Global Queue)的函數,在封裝函數之前我們先來聊聊什麼是全域隊列。全域隊列是系統提供的一個隊列,該隊列拿過來就能用,按執行方式來說,全域隊列應該稱得上是並行隊列,關於串並行隊列的具體概念下方會給出介紹。我們在擷取全域隊列的時候要知道其隊列的優先順序,優先順序越高的隊列就越先執行,當然該處的優先順序不是絕對的。隊列真正的執行順序還需要根據CUP當前的狀態來定,大部分是按照你指定的隊列優先順序來執行的,不過也有例外。下方執行個體會給出詳細的介紹。下方就是我們擷取全域隊列的函數,在擷取全域隊列為為全域隊列指定一個優先順序,預設為DISPATCH_QUEUE_PRIORITY_DEFAULT。

  3.建立串列隊列與並行隊列

因為我們在實現執行個體時會建立一些並行隊列和串列隊列,所以我們要對並行隊列的建立於串列隊列的建立進行提取。GCD中是調用dispatch_queue_create()函數來建立我們想要的線程的。dispatch_queue_create()函數有兩個參數,第一個參數是隊列的標示,用來標示你建立的隊列對象,一般是網域名稱的倒寫如“cn.zeluli”這種形式。第二個參數是所建立隊列的類型,DISPATCH_QUEUE_CONCURRENT就說明建立的是並行隊列,DISPATCH_QUEUE_SERIAL表明你建立的是串列隊列。至於兩者的區別,還是那句話,下方執行個體中會給出詳細的介紹。

  

 

 

二、同步執行與非同步執行

同步執行可分為串列隊列的同步執行和並行隊列的同步執行,而非同步執行又可分為串列隊列的非同步執行和並行隊列的非同步執行。也許聽起來有些拗口,不通過下方的圖解你會很好的理解這一概念。上一部分算是我們的準備工作,接下來才是我們真正的主題。在第一部分我們實現了擷取當前線程,對當前線程休眠,擷取主隊列和全域隊列以及建立並行隊列和串列隊列。在本部分將要利用上述函數來進一步討論串列隊列與並行隊列的同步執行,以及串列隊列與並行隊列的非同步執行。並且會給出同步執行與非同步執行的區別。

在聊同步執行與非同步執行之前我們先聊聊串列隊列(Serial Queue)與並行隊列(Concurrent Queue)的區別。無論是Serial Queue還是Concurrent Queue,都是隊列,只要是隊列都遵循FIFO(First In First Out -- 先入先出)的規則,排隊嘛,當然是誰先來的誰先走了。不過在Serial Queue中要等到前面的任務出隊列並執行完後,下一個任務才能出隊列進行執行。而Concurrent Queue則不然,只要是隊列前面的任務出隊列了,並且還有有空餘線程,不管前面的任務是否執行完了,下一任務都可以進行出隊列。

關於串列隊列和並行隊列的問題,我們可以拿銀行辦業務排隊來類比一下。比如你現在在串列隊列中排的是1號視窗,你必須等前面一個人在1號視窗辦完業務你才可以去1號視窗中去辦你的業務,就算其他視窗空著你也不能去,因為你選擇的是串列隊列。但是如果你是在並行隊列中的話,只要你前面的人去視窗中辦業務了,此時你無需關係你前面的人的業務是否辦完了,只要有其他視窗空著你就可以去辦理你的業務。總結一下:串列隊列就是認準一個線程,一條道走到黑,比較專註;並行隊列就是能利用其他線程就利用,比較靈活,不鑽牛角尖。接下來我們要看一下兩個隊列的不同執行方法。

1.同步執行

首先我們先來介紹同步執行,關於同步執行的主要執行個體對應著“同步執行串列隊列”和“同步執行並行隊列”這兩個按鈕。Serial Queue可以同步執行,Concurrent Queue亦可以同步執行。我們先拋開隊列,看一下同步執行的代碼如何。下方的函數就是對同步執行的任務進行封裝。同步執行就是使用dispatch_sync()方法進行執行。在下方函數中通過for-in迴圈以同步執行的方式往queue(隊列)中添加了3個Block執行塊。函數的參數是隊列類型(dispatch_queue_t),可以給該函數傳入串列隊列和並行隊列。

  

 

也就是說要同步執行串列隊列就給函數傳入串列隊列的對象,如果要同步執行並行隊列就傳入並行隊列對象。此時我們就用到了之前封裝的建立串列隊列和並行隊列的方法(參見第一部分)。下方程式碼片段就是點擊“同步執行串列隊列”和“同步執行並行隊列”這兩個按鈕所做的事情。點擊“同步執行串列隊列”按鈕時就建立一個串列隊列的對象傳給上面同步執行的函數(performQueuesUseSynchronization()),點擊“同步執行並行隊列”按鈕時就建立一個並行隊列的對象給上面的函數。

  

 

下方是點擊兩個按鈕所啟動並執行結果。紅框中是同步執行串列隊列的結果,可以看出來是在當前線程(主線程)下按著FIFO的順序來執行的。而綠框中的是同步執行並行隊列的運行結果,從結果中部門不難看出,與紅框中的結果一致,也是在當前線程中按著FIFO的順序來執行的。

  如果當前線程是主線程的話,那麼就會阻塞主線程,因為主線程被阻塞了,就會會造成UI卡死的現象。因為同步執行是在當前線程中來執行的任務,也就是說現在可以供隊列使用的線程只有一個,所以串列隊列與並行隊列使用同步執行的結果是一樣的,都必須等到上一個任務出隊列並執行完畢後才可以去執行下一個任務。我們可以使用同步執行的這個特點來為一些代碼塊加同步鎖。下方就是上面代碼以及執行結果的描述圖。

   

 

2、非同步執行

接下來我們看非同步執行,同樣非同步執行也分為串列隊列的非同步執行和並行隊列的非同步執行。在GCD中使用dispatch_async()函數來進行非同步執行,dispatch_async()函數的參數與dispatch_sync()函數的參數一致。只不過是dispatch_async()非同步執行不會在當前線程中執行,它會開闢新的線程,所以非同步執行不會阻塞當前線程。下方程式碼片段就是我們封裝的非同步執行的函數,其中主要是對dispatch_async()函數的使用。下方為了讓隊列中的Block的三個輸出語句順序輸出,我們將其放在了一個同步隊列中來執行,從而這三個輸出語句可以順序執行。

  

 

(1)、串列隊列的非同步執行

有了上面的函數後,我們就可以給上面的函數傳入Serial Queue隊列的對象,從而觀察串列隊列非同步執行結果。對應這我們第一張中的“非同步執行串列隊列”的按鈕,下方是點擊該按鈕執行的方法。在該按鈕點擊的方法中我們調用了performQueuesUseAsynchronization()方法,並且傳入了一個串列隊列。也就是串列隊列的非同步執行。

  

點擊按鈕就會執行上述方法,下方是點擊按鈕後,也就是“非同步執行串列隊列”時在控制台中輸出的結果。從輸出結果中我們不難看出,非同步執行並沒有阻塞當前線程。使用dispatch_saync()開闢了新的線程(線程的number = 3)來執行Block中的內容。而Block內容外的東西依然在之前的線程(在該樣本中是main_thread)中進行執行。從下方的結果中來分析,就是for迴圈執行完畢後主線程的任務就結束了,至於Block中的內容就交給新開闢的線程3來執行了。

  

 

根據上面的輸出結果,我們可以畫出下方非同步執行串列隊列的分析圖。線上程1中的一個串列隊列如果使用非同步執行的話,會開闢一個新的線程2來執行隊列中的Block任務。在新開闢的線程中依然是FIFO, 並且執行順序是等待上一個任務執行完畢後才開始執行下一個任務。如下所示。

   (2)、並行隊列的非同步執行

接下來來討論一下並行隊列的非同步執行方式。其實並行隊列與非同步執行方式相結合才能大大的提供效率,因為使用非同步執行並行隊列時會開闢多個線程來同時執行並行隊列中的任務。比如現在開闢了10個線程,那麼非同步隊列會根據FIFO的順序出來10個任務,這10個任務會進入到不同的線程中來執行,至於每個任務執行完的先後順序由每個任務的複雜度而定。非同步隊列的特點是只要有可用的線程,任務就會出隊列進行執行,而不關心之前出隊列的任務(Block)是否執行完畢。下方的方法就是點擊“非同步執行並行隊列”按鈕所調用的方法。該方法會調用performQueuesUseAsynchronization()函數,並傳入一個並行隊列的對象。

  

點擊按鈕就會執行上述方法,並行隊列就會非同步執行。下方結果就是並行隊列非同步執行後輸出的結果,解析來讓我們來分析一下輸出結果。下方第一個紅框中是並行隊列中任務的順序,由前到後為0、1、2,緊接著是每個任務執行後所輸出的結果。從任務執行完列印結果我們可以看出,執行完成的順序是2、1、0,每個任務都會在一個新的線程中來執行的。如果你在點擊一下按鈕,執行完成的順序有可能是2、0、1等其他的順序,所以並行隊列非同步執行中每個任務結束時間有主要由任務本身的複雜度而定的。

  

 根據上面的執行結果,我們畫出了下方的解說圖。當並行隊列非同步執行時會開闢多個新的線程來執行隊列中的任務,隊列中的任務出隊列的順序仍然是FIFO,只不過是不需要等到前面的任務執行完而已,只要是有空餘線程可以使用就可以按FIFO的順序出隊列進行執行。 

  

 

 

三、順延強制

在GCD中我們使用dispatch_after()函數來順延強制隊列中的任務, dispatch_after()是非同步執行隊列中的任務的,也就是說使用dispatch_after()來執行隊列中的任務不會阻塞當前任務。等到延遲時間到了以後就會開闢一個新的線程然後執行隊列中的任務。要注意一點是延遲時間到了後再開闢新的線程然後立即執行隊列中的任務。下方是dispatch_after()函數的使用方式。

在下方代碼中使用了兩種方式來建立延遲時間,一個是使用dispatch_time()來建立延遲時間,另一個是使用dispatch_walltime()來建立時間。前者是取的是當前裝置的時間,後者去的是掛鐘的時間,也就是絕對時間,如果裝置休眠了那麼前者也就休眠了,而後者是是根據掛鐘時間不會有當前裝置的狀態而左右的。下面在建立dispatch_time_t對象的時候,有一個參數是NSEC_PER_SEC,從命名只能怪我們就可以知道NSEC_PER_SEC表示什麼意思,就是每秒包含多少納秒。你可以將該值進行列印,發現NSEC_PER_SEC = 1_000_000_000。也就是一秒等於10億納秒。如果下方的time不乘於NSEC_PER_SEC那麼就代表1納秒的時間,也就是說此處的時間是按納秒(nanosecond)來計算的。下方就是順延強制的的代碼,因為改代碼輸出結果比較簡單,在此就不做過多的贅述了。需要注意的是順延強制會在新開闢的隊列中進行執行,不會阻塞新的線程。

  

 

四、隊列的優先順序

隊列也是有優先順序的,但其優先順序不是絕對的大部分情況因為XUN核心用於GCD不是即時性的,優先順序只是大致的來判斷隊列的執行優先順序。隊列分為四個優先順序,由高到底分別是High > Default > Low > Background。上面在擷取全域隊列時我們可以為擷取的隊列指定優先順序,並且可以使用dispatch_set_target_queue()函數將一個隊列的優先順序賦值給另一個隊列。下方我們先給全域隊列指定優先順序,然後在將其賦值給其他隊列。

1.為全域隊列指定優先順序

本部分對應著“設定全域隊列的優先順序”這個button,點擊該button就會擷取4個不同優先順序的全域隊列,然後非同步進行全域隊列的執行,最後觀察執行的結果。下方就是點擊該按鈕所要執行的函數。我先擷取了四種不同優先順序的全域隊列,然後進行非同步執行,並列印執行結果。

  

 

上述代碼的運行結果如下,雖然在上述代碼中優先順序高的代碼放在了最後來進行非同步執行,可是卻先被列印了。列印的順序是Hight->Default->Low->Background,這個列印順序就是其執行順序,從列印順序中我們不難看出優先順序高的先被執行。當然這不是絕對的。

   

 

2. 為自建立的隊列指定優先順序

在GCD中你可以使用dispatch_set_target_queue()函數為你自己建立的隊列指定優先順序,這個過程還需藉助我們的全域隊列。下方的程式碼片段中我們先建立了一個串列隊列,然後通過dispatch_set_target_queue()函數將全域隊列中的高優先順序賦值給我們剛建立的這個串列隊列,如下所示。

  

 

五、工作群組dispatch_group

GCD的工作群組在開發中是經常被使用到,當你一組任務結束後再執行一些操作時,使用工作群組在合適不過了。dispatch_group的職責就是當隊列中的所有任務都執行完畢後在去做一些操作,也就是說在工作群組中執行的隊列,當隊列中的所有任務都執行完畢後就會發出一個通知來告訴使用者工作群組中所執行的隊列中的任務執行完畢了。關於將隊列放到工作群組中執行有兩種方式,一種是使用dispatch_group_async()函數,將隊列與工作群組進行關聯並自動執行隊列中的任務。另一種方式是手動的將隊列與組進行關聯然後使用非同步將隊列進行執行,也就是dispatch_group_enter()與dispatch_group_leave()方法的使用。下方就給出詳細的介紹。

1.隊列與組自動關聯並執行

首先我們來介紹dispatch_group_async()函數的使用方式,該函數會將隊列與相應的工作群組進行關聯,並且自動執行。當與工作群組關聯的隊列中的任務都執行完畢後,會通過dispatch_group_notify()函數發出通知告訴使用者工作群組中的所有任務都執行完畢了。使用通知的方式是不會阻塞當前線程的,如果你使用dispatch_group_wait()函數,那麼就會阻塞當前線程,直到工作群組中的所有任務都執行完畢。

下方封裝的函數就是使用dispatch_group_async()函數將隊列與工作群組進行關聯並執行。首先我們建立了一個concurrentQueue並行隊列,然後又建立了一個類型為dispatch_group_t的工作群組group。使用dispatch_group_async()函數將兩者進行關聯並執行。使用dispatch_group_notify()函數進行監聽group中隊列的執行結果,如果執行完畢後,我們就在主線程中對結果進行處理。dispatch_group_notify()函數有兩個參數一個是發送通知的group,另一個是處理返回結果的隊列。

  

 

調用上述函數的輸出結果如下。從輸出結果中我們不難看出,隊列中任務的執行以及通知結果的處理都是非同步執行的,不會阻塞當前的線程。在工作群組中所有任務都處理完畢後,就會在主線程中執行dispatch_group_notify()中的閉包塊。

  

 

2. 手動關聯隊列與工作群組

接下來我們將手動的管理工作組與隊列中的關係,也就是不使用dispatch_group_async()函數。我們使用dispatch_group_enter()與dispatch_group_leave()函數將隊列中的每次任務加入到到工作群組中。首先我們使用dispatch_group_enter()函數進入到工作群組中,然後非同步執行隊列中的任務,最後使用dispatch_group_leave()函數離開工作群組即可。下面的函數中我們使用了dispatch_group_wait()函數,該函數的職責就是阻塞當前線程,來等待工作群組中的任務執行完畢。該函數的第一個參數是所要等待的group,第二個參數是等待逾時時間,此處我們設定的是DISPATCH_TIME_FOREVER,就說明等待工作群組的執行永不逾時,直到工作群組中所有任務執行完畢。

  

下方是上述函數執行後的輸出結果,dispatch_group_wait()函數下方的print()函數在所有任務執行完畢之前是不會被調用的,因為dispatch_group_wait()會將當前線程進行阻塞。當然雖然是手動的將隊列與工作群組進行關聯的,display_group_notify()函數還是好用的。運行結果如下所示。

  

 

六、訊號量(semaphore)同步鎖

有時候多個線程對一個資料進行操作的時候,為了資料的一致性,我們只允許一次只有一個線程來操作這個資料。為了保證一次只有一個線程來修改我們的資源資料,我們就得用到訊號量同步鎖了。也就是說一個存放資源房間的門後邊又把鎖,當有一個線程進到這個房間後就將這把鎖鎖上。當這個線程修改完該資源後,就將鎖給開啟,鎖開啟後其他的線程就可以持有資源了。如果你上了鎖不打卡,而其他線程等待使用該資源時,就會產生死結。所以當你不使用的時候,就不要持有資源呢。

上述這個過程,在GCD中我們可以使用訊號量機制來完成。在GCD中有一個叫dispatch_semaphore_t的東西,這個就是我們的訊號量。我們可以對訊號量進行操作,如果訊號量為0那麼就是上鎖的狀態,其他線程想使用資源就得等待了。如果訊號量不為零,那麼就是開鎖狀態,開鎖狀態下資源就可以訪問。下方代碼就是訊號量的具體使用代碼。

下方第一個紅框中就是通過dispatch_semaphore_create()來建立訊號量,該函數需要一個參數,該參數所指定的就是訊號量的值,我們為訊號指定的值為1。第二個紅框中是“上鎖的過程”,通過dispatch_semaphore_wait()函數對訊號量操作,該函數中的第一個參數是所操作的訊號量,第二個參數是等待時間。dispatch_semaphore_wait()函數是對訊號量減一,訊號量為零的話就對當前線程所操作的資源加鎖。其他線程等待當前線程操作資源的時間為DISPATCH_TIME_FOREVER,也就是說其他線程要一直等下去,等待當前線程操作資源完畢。噹噹前線程對資源操作完畢後調用dispatch_semaphore_signal()將訊號量加1,將資源進行解鎖,以便於其他等待的線程進行資源的訪問。當解鎖後,其他線程等待的時間結束,就可以進行資源的訪問了。

  

 

七、隊列的迴圈、掛起、恢複

在本篇部落格的第七部分,我們要聊一下隊列的迴圈執行以及隊列的掛起與恢複。該部分比較簡單,但是也是比較常用的。在重複執行隊列中的任務時,我們通常使用dispatch_apply()函數,該函數迴圈執行隊列中的任務,但是dispatch_apply()函數本身會阻塞當前線程。如果你使用dispatch_apply()函數來執行並行隊列,雖然會開啟多個線程來迴圈執行並行隊列中的任務,但是仍然會阻塞當前線程。如果你使用dispatch_apply()函數來執行串列隊列的話,那麼就不會開闢新的線程,當然就會將當前線程進行阻塞。說到隊列的掛起與恢複你可以使用dispatch_suspend()來掛起隊列,使用dispatch_resum()來恢複隊列。請看下方執行個體。

1、dispatch_apply()函數

dispatch_apply()函數是用來迴圈來執行隊列中的任務的,使用方式為:dispatch_apply(迴圈次數, 任務所在的隊列) { 要迴圈執行的任務 }。使用該函數迴圈執行並行隊列中的任務時,會開闢新的線程,不過有可能會在當前線程中執行一些任務。而使用dispatch_apply()執行串列隊列中的任務時,會在當前線程中執行。無論是使用並行隊列還是串列隊列,dispatch_apply()都會阻塞當前線程。下方程式碼片段就是dispatch_apply()的使用樣本:

  

下方則是上述函數的運行結果。在結果中我們將每次執行任務所使用的線程進行了列印。

  

 

2. 隊列的掛起與喚醒

隊列的掛起與喚醒相對較為簡單,如果你想對一個隊列中的任務的執行進行掛起,那麼你就使用dispatch_suspend()函數即可。如果你要喚醒某個掛起的隊列,那麼你就可以使用dispatch_resum()函數。這兩個函數所需的參數都是你要掛起或者喚醒的隊列,鑒於知識點的簡單性就不做過多的贅述了。下方是對非同步執行的並行隊列進行掛起,在當前線程休眠2秒後喚醒被掛起的線程。具體代碼如下:

  

 

八、任務柵欄dispatch_barrier_async()

顧名思義,任務柵欄就是將隊列中的任務進行隔離的,是任務能分撥的進行非同步執行。我想用下方的圖來介紹一下barrier的作用。我們假設下方是並行隊列,然後並行隊列中有1.1、1.2、2.1、2.2四個任務,前兩個任務與後兩個任務本中間的柵欄給隔開了。如果沒有中間的柵欄的話,四個任務會在非同步情況下同時執行。但是有柵欄的攔著的話,會先執行柵欄前面的任務。等前面的任務都執行完畢了,會執行柵欄內建的Block ,最後非同步執行柵欄後方的任務。這麼一說有點與前面的dispatch_group類似,當執行完一些列的任務後,我們想做一些事情的話,我們也可通過dispatch_barrier_async()來實現。

  

下方程式碼片段就是我們dispatch_barrier_async(), 具體的使用方式。上面的紅色框中的代碼是非同步執行的第一批任務,中間是我們給任務隊列添加的任務柵欄,dispatch_barrier_asyn()的一個參數就是柵欄所在的隊列,而後邊的尾隨閉包就是在柵欄前面的所有任務都執行完畢後就會執行該尾隨閉包中的內容。而最下方黃色框中的部分就是第二批次執行的任務,該批任務會在dispatch_barrier_asyn()柵欄的尾隨閉包執行後會繼續執行。

  

接下來我們來看一下上述代碼的運行結果,點擊我們第一部分的“使用任務隔離柵欄”按鈕就會執行上述方法。下方就是上述程式碼片段的運行結果。從下面的輸出結果中不難看出,dispatch_barrier_asyn之前的任務會先非同步執行,也就是下方的第一批任務。第一批任務完成後,會在第一批任務中的最後完成任務的線程中來執行柵欄中的任務塊。當柵欄中的任務執行完畢後,隊列中的第二批任務中的第一個會進入執行柵欄任務的線程中來執行,其他的會開闢新的線程。如下所示。

  

我們可以用一個圖來結合上述樣本來解釋柵欄的工作方式。畫的就是柵欄工作的方式,需要注意的是隊列中的第一批任務中的最後一個任務與柵欄中的任務已經第二批第一個任務是用一個線程來執行的。這就是為什麼柵欄能進行任務隔離的根本了。從下方的圖中我們不難發現,任務1.3、柵欄任務、任務2.1線上程5中是同步執行的。具體請看。

  

九、dispatch_source

dispatch_source在GCD中是一個比較靈活的東西,功能也是非常強大的。簡單的說,dispatch_source的主要功能就是對某些類型事件的對象進行監聽,當事件發生時將要處理的事件放到關聯的隊列中進行執行。dispatch源支援事件的取消,我們也可以對取消事件的進行處理。下方是dispatch源的不同類型,因為篇幅有限在此就不做過多的贅述了,關於這些類型的資料網上一抓一大把。今天就以DATA_ADD, DATA_OR, TIMER為例,看一下source的使用方式。

  

 

1. DATA_ADD 與DATA_OR

DISPATCH_SOURCE_TYPE_DATA_ADD和DISPATCH_SOURCE_TYPE_DATA_OR用法差不多一個是將資料來源進行相加,一個是進行或操作。我們就以相加的為例,或操作的代碼在部落格中就不給出了,不過我們github上分享的代碼會有完整的樣本。下方函數是DISPATCH_SOURCE_TYPE_DATA_ADD類型的dispatch源的使用。

首先我們擷取了一個全域隊列queue,然後建立了一個dispatch源,命名為dispatchSource。在建立dispatch源時,我們為dispatch源指定了類型,並且為其關聯的一個queue隊列。關聯這個隊列的作用是用來處理dispatch源中的事件的。然後我們使用dispatch_source_set_event_handler()為我們的source指定處理事件。該事件會在一定的條件下回觸發,至於觸發的條件有dispatch源的類型鎖定。因為此處我們dispatch源的類型是DISPATCH_SOURCE_TYPE_DATA_ADD,所以使用dispatch_source_merge_data()就可以觸發上面我們指定的事件。因為dispatch源建立後是處於掛起的狀態,所以我們需要使用dispatch_resume()對我們建立的事件來源進行恢複。恢複後的dispatch源才可以監聽條件從而觸發事件。

下方程式碼片段在for迴圈中調用dispatch_source_merge_data()方法。在執行過程中我們還可以調用dispatch_source_cancel()對dispatch源進行取消。當dispatch source被取消後,就會執行我們所設定取消dispatch_source要處理的事件。我們通過dispatch_source_set_candel_handel()來指定取消dispatch source要執行的事件。關於dispatch_source的取消,我們會在下面倒計時的時候給出。

我們此處建立的dispatch_source的類型是Data Add類型,也就是說當我們指定的源事件未處理完時,那麼下一個Data就要進行等待。而等待的資料會通過dispatch_source_merge_data()方法進行合并。如果你建立的是DISPATCH_SOURCE_TYPE_DATA_ADD類型的dispatch_source,那麼就會按照加法進行合并。如果你是建立的DISPATCH_SOURCE_TYPE_DATA_OR類型的dispatch_source, 那麼就會通過或運算進行合并。合并在一起的資料會一同觸發我們設定的事件。

  

上述程式碼片段就是對DATA_ADD類的的dispatch源進行的測試。我們定義了一個變數sum來類比資料的合并,然後觀察每次合并的資料與我們自定的sum中計算的資料是否相同。合并後每次執行一次事件我們都將sum進行歸零,然後進行下一輪的合并。下方就是上述代碼輸出的結果。從下方的結果中我們可以看出,在上述的10次迴圈中執行了四次我們指定的source事件,而且每次執行事件所merge的Data與我們手動記錄的sum一致。這就是DATA_ADD的工作方式,運行效果如下所示。關於Data_Or的運行方式在此就不做過多的贅述了。

  

 

2.定時器

在GCD的dispatch源中還有定時器類型,我們可以建立定時器類型的dispatch源,然後通過dispatch_source_set_event_handler()來設定源事件。然後通過dispatch_source_set_timer()函數來為定時器類型的dispatch_source指定時間間隔,該函數第一個參數就是dispatch source,第二個參數就是觸發事件的時間間隔,第三個參數就是允許誤差的時間。當我們設定的倒計時的次數到是,我們就調用dispatch_source_cancel()來進行dispatch_source的取消,當取消後就會執行dispatch_source_set_cancel_handel()方法中的尾隨閉包。

下方樣本是使用DISPATCH_SOURCE_TYPE_TIMER類型的dispatch source進行的10秒到計時,等我們設定的事件執行10次後我們就取消dispatch_source。對於下方的樣本來說,當dispatch source通過dispatch_resume()函數進行喚醒後,會開始倒計時。會在倒計時10秒後結束計時。

  

 

下方就是上述倒計時代碼所執行後的結果。從運行結果中我們不難看出,當倒計時開始時,會新開闢一些新的線程來順序執行倒計時任務。儘管你使用的是並行隊列,雖然每次開闢的線程可能會不同,但是都會順序的執行倒計時任務,

  

 

今天部落格的內容也夠多的了,應該說還算是幹活滿滿,上述所有代碼將會在github上進行分享,下方是分享地址。有什麼問題,或者需要補充的加QQ群吧(573884471),之前的群人已經滿了,加不進去了,就建立了一個新的。

github分享地址為:https://github.com/lizelu/GCDDemo-Swift

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.