iOS dispatch_source_t的理解

时间:2021-01-13 20:32:56

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_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 事件处理器...")      
}

例子:

__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相关信息的函数,比如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函数实现: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_suspenddispatch_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