dispatch_source_t


  • 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:監聽自定義事件的調度源。

這一節就來看看如何使用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:第四個參數設置回調函數所在的隊列。
let dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)        let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatchQueue)

上面的代碼就是創建Dispatch Source的簡單示例。

設置事件處理器

前文中提到過,當Dispatch Source監聽到事件時會調用指定的回調函數或閉包,該回調函數或閉包就是Dispatch Source的事件處理器。我們可以使用dispatch_source_set_event_handlerdispatch_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 事件處理器...")      }

從上面示例代碼中可以看到,該函數有兩個參數,第一個是設置目標Dispatch Source,第二個參數就是設置處理器了。

既然是事件處理器,那麼肯定需要獲取一些Dispatch Source的信息,GCD提供了三個在處理器中獲取Dispatch Source相關信息的函數,比如handlemask。而且針對不同類型的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函數實現:

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_suspenddispatch_resume函數。這裏需要注意的是剛創建好的Dispatch Source是處於暫停狀態的,所以使用時需要用dispatch_resume函數將其啓動。

廢除Dispatch Source

如果我們不再需要使用某個Dispatch Source時,可以使用dispatch_source_cancel函數廢除,該函數只有一個參數,那就是目標Dispatch Source。

Dispatch Source實踐

說了這麼多,這一節來看看Dispatch Source到底怎麼用。

用Dispatch Source監聽定時器

Dispatch Source能監聽的事件中有一個類型就是定時器,我們來看看如何實現:

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章