Dispatch Source是GCD中的一個基本類型,從字面意思可稱為調度源,它的作用是當有一些特定的較底層的系統事件發生時,調度源會捕捉到這些事件,然后可以做其他的邏輯處理,調度源有多種類型,分別監聽對應類型的系統事件。我們來看看它都有哪些類型:
- Timer Dispatch Source:定時調度源。
- Signal Dispatch Source:監聽UNIX信號調度源,比如監聽代表掛起指令的SIGSTOP信號。
- Descriptor Dispatch Source:監聽文件相關操作和Socket相關操作的調度源。
- Process Dispatch Source:監聽進程相關狀態的調度源。
- Mach port Dispatch Source:監聽Mach相關事件的調度源。
- Custom Dispatch Source:監聽自定義事件的調度源。
用通俗一點的話說就是用GCD的函數指定一個希望監聽的系統事件類型,再指定一個捕獲到事件后進行邏輯處理的閉包或者函數作為回調函數,然后再指定一個該回調函數執行的Dispatch Queue即可,當監聽到指定的系統事件發生時會調用回調函數,將該回調函數作為一個任務放入指定的隊列中執行。也就是說當監聽到系統事件后就會觸發一個任務,並自動將其加入隊列執行,這里與之前手動添加任務的模式不同,一旦將Diaptach Source與Dispatch Queue關聯后,只要監聽到系統事件,Dispatch Source就會自動將任務(回調函數)添加到關聯的隊列中。有些時候回調函數執行的時間較長,在這段時間內Dispatch Source又監聽到多個系統事件,理論上就會形成事件積壓,但好在Dispatch Source有很好的機制解決這個問題,當有多個事件積壓時會根據事件類型,將它們進行關聯和結合,形成一個新的事件。
監聽事件類型
Dispatch Source一共可以監聽六類事件,分為11個類型,我們來看看都是什么:
DISPATCH_SOURCE_TYPE_DATA_ADD
:屬於自定義事件,可以通過dispatch_source_get_data
函數獲取事件變量數據,在我們自定義的方法中可以調用dispatch_source_merge_data
函數向Dispatch Source設置數據,下文中會有詳細的演示。DISPATCH_SOURCE_TYPE_DATA_OR
:屬於自定義事件,用法同上面的類型一樣。DISPATCH_SOURCE_TYPE_MACH_SEND
:Mach端口發送事件。DISPATCH_SOURCE_TYPE_MACH_RECV
:Mach端口接收事件。DISPATCH_SOURCE_TYPE_PROC
:與進程相關的事件。DISPATCH_SOURCE_TYPE_READ
:讀文件事件。DISPATCH_SOURCE_TYPE_WRITE
:寫文件事件。DISPATCH_SOURCE_TYPE_VNODE
:文件屬性更改事件。DISPATCH_SOURCE_TYPE_SIGNAL
:接收信號事件。DISPATCH_SOURCE_TYPE_TIMER
:定時器事件。DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
:內存壓力事件。
創建Dispatch Source
我們可以使用dispatch_source_create
函數創建Dispatch Source,該函數有四個參數:
type
:第一個參數用於標識Dispatch Source要監聽的事件類型,共有11個類型。handle
:第二個參數是取決於要監聽的事件類型,比如如果是監聽Mach端口相關的事件,那么該參數就是mach_port_t
類型的Mach端口號,如果是監聽事件變量數據類型的事件那么該參數就不需要,設置為0就可以了。mask
:第三個參數同樣取決於要監聽的事件類型,比如如果是監聽文件屬性更改的事件,那么該參數就標識文件的哪個屬性,比如DISPATCH_VNODE_RENAME
。queue
:第四個參數設置回調函數所在的隊列。
NSTimeInterval period = 0.1; //設置時間間隔 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);//dispatch_source_t _timer 類型的
設置事件處理器
前文中提到過,當Dispatch Source監聽到事件時會調用指定的回調函數或閉包,該回調函數或閉包就是Dispatch Source的事件處理器。我們可以使用dispatch_source_set_event_handler
或dispatch_source_set_event_handler_f
函數給創建好的Dispatch Source設置處理器,前者是設置閉包形式的處理器,后者是設置函數形式的處理器:
dispatch_source_set_event_handler(dispatchSource, { print("Dispatch Source 事件處理器...")})// 根據閉包尾隨的特性,還可以有下面的寫法dispatch_source_set_event_handler(dispatchSource) { print("Dispatch Source 事件處理器...") }
例子:
__block int timeout=300; //倒計時時間 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue); dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); //沒秒執行 dispatch_source_set_event_handler(_timer, ^{ if(timeout<=0){ //倒計時結束,關閉 dispatch_source_cancel(_timer); dispatch_release(_timer); dispatch_async(dispatch_get_main_queue(), ^{ //設置界面的按鈕顯示 根據自己需求設置 。。。。。。。。 }); }else{ int minutes = timeout / 60; int seconds = timeout % 60; NSString *strTime = [NSString stringWithFormat:@"%d分%.2d秒后重新獲取驗證碼",minutes, seconds]; dispatch_async(dispatch_get_main_queue(), ^{ //設置界面的按鈕顯示 根據自己需求設置 。。。。。。。。 }); timeout--; } }); dispatch_resume(_timer);
既然是事件處理器,那么肯定需要獲取一些Dispatch Source的信息,GCD提供了三個在處理器中獲取Dispatch Source相關信息的函數,比如handle
、mask
。而且針對不同類型的Dispatch Source,這三個函數返回數據的值和類型都會不一樣,下面來看看這三個函數:
dispatch_source_get_handle
:這個函數用於獲取在創建Dispatch Source時設置的第二個參數handle
。
- 如果是讀寫文件的Dispatch Source,返回的就是描述符。
- 如果是信號類型的Dispatch Source,返回的是
int
類型的信號數。 - 如果是進程類型的Dispatch Source,返回的是
pid_t
類型的進程id。 - 如果是Mach端口類型的Dispatch Source,返回的是
mach_port_t
類型的Mach端口。
dispatch_source_get_data
:該函數用於獲取Dispatch Source監聽到事件的相關數據。
- 如果是讀文件類型的Dispatch Source,返回的是讀到文件內容的字節數。
- 如果是寫文件類型的Dispatch Source,返回的是文件是否可寫的標識符,正數表示可寫,負數表示不可寫。
- 如果是監聽文件屬性更改類型的Dispatch Source,返回的是監聽到的有更改的文件屬性,用常量表示,比如
DISPATCH_VNODE_RENAME
等。 - 如果是進程類型的Dispatch Source,返回監聽到的進程狀態,用常量表示,比如
DISPATCH_PROC_EXIT
等。 - 如果是Mach端口類型的Dispatch Source,返回Mach端口的狀態,用常量表示,比如
DISPATCH_MACH_SEND_DEAD
等。 - 如果是自定義事件類型的Dispatch Source,返回使用
dispatch_source_merge_data
函數設置的數據。
dispatch_source_get_mask
:該函數用於獲取在創建Dispatch Source時設置的第三個參數mask
。在進程類型,文件屬性更改類型,Mach端口類型的Dispatch Source下該函數返回的結果與dispatch_source_get_data
一樣。
注冊Cancellation Handler
Cancellation Handler就是當Dispatch Source被釋放時用來處理一些后續事情,比如關閉文件描述符或者釋放Mach端口等。我們可以使用dispatch_source_set_cancel_handler
函數或者dispatch_source_set_cancel_handler_f
函數給Dispatch Source注冊Cancellation Handler:
dispatch_source_set_cancel_handler(dispatchSource) { print("進行善后處理...") }
該函數有兩個參數,第一個參數是目標Dispatch Source,第二個參數就是要進行善后處理的閉包或者函數。
更改Dispatch Source的目標隊列
在上文中,我們說過可以使用dispatch_source_create
函數創建Dispatch Source,並且在創建時會指定回調函數執行的隊列,那么如果事后想更改隊列,比如說想更改隊列的優先級,這時我們可以使用dispatch_set_target_queue
函數實現:swift
let dispatchQueueDefaultPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatchQueueDefaultPriority) let dispatchQueueLowPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0) dispatch_set_target_queue(dispatchSource, dispatchQueueLowPriority)
這里需要注意的是,如果在更改目標隊列時,Dispatch Source已經監聽到相關事件,並且回調函數已經在之前的隊列中執行了,那么會一直在舊的隊列中執行完成,不會轉移到新的隊列中去。
暫停恢復Dispatch Source
暫停和恢復Dispatch Source與Dispatch Queue一樣,都適用dispatch_suspend
和dispatch_resume
函數。這里需要注意的是剛創建好的Dispatch Source是處於暫停狀態的,所以使用時需要用dispatch_resume
函數將其啟動。
廢除Dispatch Source
如果我們不再需要使用某個Dispatch Source時,可以使用dispatch_source_cancel
函數廢除,該函數只有一個參數,那就是目標Dispatch Source。
Dispatch Source實踐
說了這么多,這一節來看看Dispatch Source到底怎么用。
用Dispatch Source監聽定時器
Dispatch Source能監聽的事件中有一個類型就是定時器,我們來看看如何實現:swift
class TestDispatchSource {
func launch() {
let dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
let timer = createTimerDispatchSource(dispatch_time(DISPATCH_TIME_NOW, 0), interval: NSEC_PER_SEC * 5, leeway: 0, queue: dispatchQueue) {
print("處理定時任務,該任務每5秒執行一次...") } dispatch_resume(timer) sleep(30) }
func createTimerDispatchSource(startTime: dispatch_time_t, interval: UInt64, leeway: UInt64, queue: dispatch_queue_t, handler: dispatch_block_t) -> dispatch_source_t {
let timerDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
dispatch_source_set_timer(timerDispatchSource, startTime, interval, leeway)
dispatch_source_set_event_handler(timerDispatchSource, handler)
return timerDispatchSource }}
上面的代碼示例中一個新的函數dispatch_source_set_timer
,該函數的作用就是給監聽事件類型為DISPATCH_SOURCE_TYPE_TIMER
的Dispatch Source設置相關屬性,該函數有四個參數:
source
:該參數為目標Dispatch Source,類型為dispatch_source_t
.start
:該參數為定時器的起始時間,類型為dispatch_time_t
。interval
:該參數為定時器的間隔時間,類型為UInt64
,間隔時間的單位是納秒。leeway
:該參數為間隔時間的精度,類型為UInt64
,時間單位也是納秒。
用Dispatch Source監聽自定義事件
Dispatch Source能監聽的事件中有一個類型是自定義事件,下面我們來看看如何使用:
class TestDispatchSource { func launch() { var totalProcess = 0 let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue()) dispatch_source_set_event_handler(dispatchSource) { let process = dispatch_source_get_data(dispatchSource) totalProcess += Int(process) print("這里可以在主線程更新UI,顯示進度條...進度為/(totalProcess)%") } dispatch_resume(dispatchSource) generateCustomEvent(dispatchSource) } func generateCustomEvent(dispatchSource: dispatch_source_t) { let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) for index in 0...100 { dispatch_sync(queue) {
print("模擬自定義事件...進度為/(index)%")
dispatch_source_merge_data(dispatchSource, 1) sleep(2) } } } }
我們來看看generateCustomEvent(dispatchSource: dispatch_source_t)
方法,該方法的作用的是模擬自定義事件,首先創建一個全局並發隊列,然后循環讓其執行任務,在執行的任務里調用dispatch_source_merge_data
函數,就可以觸發監聽類型為DISPATCH_SOURCE_TYPE_DATA_ADD
或者DISPATCH_SOURCE_TYPE_DATA_OR
的Dispatch Source。該函數有兩個參數,第一個參數是目標Dispatch Source,第二個參數的類型是無符號長整型,用於向目標Dispatch Source中的對應變量追加指定的數。
我們再來看看如何監聽自定義時間,首先創建類型為DISPATCH_SOURCE_TYPE_DATA_ADD
的Dispatch Source,然后設置回調閉包,在閉包中使用dispatch_source_get_data
獲取追加的變量值,該函數只有一個參數,就是目標Dispatch Source,這里需要注意的是通過dispatch_source_get_data
函數獲取的變量值並不是累加值,而是每次調用dispatch_source_merge_data
函數時設置的值,所以在上面的示例中用totalProcess
變量累加每次獲取到的值。
上面的示例可以用來模擬后台進行下載,根據下載的數據量使用dispatch_source_merge_data
函數給目標Dispatch Source設置相應的變量值,然后在主線程中監聽到Dispatch Source的自定義事件,通過dispatch_source_get_data
函數獲取到變量,用於更新顯示進度條的UI。
原文鏈接:http://www.th7.cn/Program/IOS/201605/849625.shtml
參考:http://blog.csdn.net/kut00/article/details/8845351