你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> iOS開發之再探多線程編程:Grand Central Dispatch詳解

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

編輯:IOS開發綜合

之前關於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的順序來執行的。

 

\

 

通過上面兩種不同隊列的同步執行方式我們給出了下面的分析圖。Serial Queue與Concurrent Queue中都有4個Block(編號為1--4),然後使用dispatch_sync()來同步執行。由上述示例我們可以得出,同步執行方式,也就是使用dispatch_sync()來執行隊列不會開辟新的線程,會在當前線程中執行任務。如果當前線程是主線程的話,那麼就會阻塞主線程,因為主線程被阻塞了,就會會造成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

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved