Swift4.0 GCD

Swift中GCD和OC中GCD的是使用基本一致, 最大的区别就是语法, 当然还有一些细微的改动.本文主要对GCD中的一些重难点进行解释说明, 如果以前没有接触过GCD可以

目录:

  1. 任务和队列

  2. 任务和队列Qos

  3. DispatchGroup

  4. DispatchBarrier

  5. DispatchSemaphore

  6. DispatchSourceTimer(DispatchSource的一种)

  7. DispatchSource详解

DispatchQueue: 任务和队列

学习 GCD 之前,先来了解 GCD 中两个核心概念:任务和队列。

任务

任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在 GCD 中是放在 block 中的。执行任务有两种方式:同步执行(sync)和异步执行(async)。两者的主要区别是:是否等待队列的任务执行结束,以及是否具备开启新线程的能力。

  • 同步执行(sync): 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。 只能在当前线程中执行任务,不具备开启新线程的能力。

  • 异步执行(async): 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。 可以在新的线程中执行任务,具备开启新线程的能力。

注意:异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关(主队列异步执行就不会开启新线程)。

队列

队列(Dispatch Queue):这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。队列的结构可参考下图:

在 GCD 中有两种队列:串行队列和并发队列。两者都符合 FIFO(先进先出)的原则。两者的主要区别是:执行顺序不同,以及开启线程数不同。

  • 串行队列(Serial Dispatch Queue): 每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

  • 并发队列(Concurrent Dispatch Queue): 可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务) 注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效

  • 队列(FIFO): 任务代码块的集合, 将任务放入队列后其他有系统托管

  • 同步和异步: 执行队列的方式

图表:

执行方式 并发队列 串行队列 主队列
同步(sync) 没有开启新线程,按顺序执行任务 没有开启新线程,按顺序执行任务 死锁
异步(async) 开启新线程,并行执行任务 只开启1条新线程,按顺序执行任务 没有开启新线程,在主队列按顺序执行任务
  1. 创建串行队列
1
2
		 //串行
		 let queue2 = DispatchQueue(label: "com.heron")
  1. 创建并行队列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
        
 /* 并行
 label: 标签, 方便调试
 qos: 优先级
 类attributes:类型为选项集合(option sets),包含两个选项:

	-	concurrent:标识队列为并行队列
	- initiallyInactive:标识运行队列中的任务需要手动触发,由队列的 activate 方法进行触发。如果未添加此标识,向队列中添加的任务会自动运行。
	如果不设置该值,则表示创建串行队列。如果希望创建并行队列,并且需要手动触发,则该值需要设置为 [.concurrent, .initiallyInactive],

 autoreleaseFrequency: 回收频率. autoreleaseFrequency 的类型为枚举(enum),用来设置负责管理任务内对象生命周期的 autorelease pool 的自动释放频率。包含三个类型:

	- inherit:继承目标队列的该属性,
	- workItem:跟随每个任务的执行周期进行自动创建和释放
	- never:不会自动创建 autorelease pool,需要手动管理。
	一般采用 workItem 行了。如果任务内需要大量重复的创建对象,可以使用 never 类型,来手动创建 aotorelease pool。
	
 target: 目标
 */
let queue1 = DispatchQueue(label: "com.heron", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)

Qos: 队列优先级

并不是绝对准确, 如果将循环改为100,就会出现错乱, 案例如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func qosOfQueue() -> Void {
        for _ in 0 ..< 10 {
            DispatchQueue(label: "queue", qos: .background, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil).async {
                sleep(1)
                print("background===\(Thread.current)")
            }
        
        DispatchQueue(label: "queue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil).async {
            sleep(1)
            print("default===\(Thread.current)")
        }
        
        DispatchQueue(label: "queue", qos: .userInitiated, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil).async {
            print("userInitiated===\(Thread.current)")
        }
        
        DispatchQueue(label: "queue", qos: .userInteractive, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil).async {
            sleep(1)
            print("userInteractive===\(Thread.current)")
        }
    }
}

####Qos: 任务优先级 首先介绍DispatchWorkItem, 它将block代码块任务封装成对象的DispatchWorkItem. DispatchWorkItem是对队列中任务的封装, 和block任务快效果一样

优点:

  1. DispatchWorkItem有优先级, 能限定一个队列中所有item执行的先后顺序, 同样优先级不是很靠谱, 慎用

  2. 能取消
  3. 能使用notify设置item的依赖关系

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func workItem() -> Void {
    let queue = DispatchQueue.global()
    let workItem1 = DispatchWorkItem {
        sleep(1)
        print("item 1")
    }
    let workItem2 = DispatchWorkItem {
        sleep(1)
        print("item 2")
    }
    //设置item的优先级, 并强制优先级执行
    let workItem3 = DispatchWorkItem(qos: .userInitiated, flags: .enforceQoS) {
        sleep(1)
        print("item 3")
    }
    
    
    //取消任务, 一旦取消就永远不能再执行了
    //        workItem2.cancel()
    //item1执行完成之后执行item2
    //        workItem1.notify(queue: queue, execute: workItem2)
    queue.async(execute: workItem1)
    queue.async(execute: workItem2)
    queue.async(execute: workItem3)
    
    //没弄明白这个是什么原理??????
    let res = workItem3.wait(timeout: DispatchTime(uptimeNanoseconds: 10000000000))
    print(res)
}

DispatchGroup

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func dispatchGroup() -> Void {
    //创建队列
    let queue = DispatchQueue(label: "queue")
    //创建调度组
    let group = DispatchGroup()
    //在在调度组中异步执行队列中的两个任务, 如下
    //任务一
    queue.async(group: group, qos: .default, flags: .inheritQoS) {
        sleep(1)
        print("a")
    }
    //任务二
    queue.async(group: group, qos: .default, flags: .inheritQoS) {
        sleep(1)
        print("b")
    }
    //两个任务完成后执行此任务
    group.notify(queue: DispatchQueue.main) {
        //会主线程更新UI, 获取做其他的事情
        print("main queue to updata UI")
    }
}

DispatchBarrier

在Swift4中, dispatchBarrier被废弃, 此功能合并到了DispatchWorkItem中的flags选项

应用: 写数据时使用,不能被读取

注意:

  1. 使用barrier时所有的workItem必须在同一个队列中, 不能使用globalQueue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

	 func dispatchBarrier() -> Void {
    
    let queue = DispatchQueue(label: "queue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
    
    let item1 = DispatchWorkItem(qos: .default, flags: .assignCurrentContext) {
        sleep(1)
        print("item 1")
    }
    //带屏障
    let item2 =  DispatchWorkItem(qos: .default, flags: .barrier) {
        sleep(3)
        print("item 2")
    }
    let item3 =  DispatchWorkItem(qos: .default, flags: .assignCurrentContext) {
        sleep(1)
        print("item 3")
    }
    let item4 =  DispatchWorkItem(qos: .default, flags: .assignCurrentContext) {
        sleep(4)
        print("item 4")
    }
    //item2是barrier, 之后1,3,执行完之后执行2, 然后等待4s再执行4
    queue.async(execute: item1)
    queue.async(execute: item3)
    queue.async(execute: item2)
    queue.async(execute: item4)
}

DispatchSemaphore信号量

一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程。

其实,这有点类似锁机制了,只不过信号量都是系统帮助我们处理了,我们只需要在执行线程之前,设定一个信号量值,并且在使用时,加上信号量处理方法就行了。

主要方法:

1
2
3
4
5
6
7
8
public func signal() -> Int

public func wait()


public func wait(timeout: DispatchTime) -> DispatchTimeoutResult

public func wait(wallTimeout: DispatchWallTime) -> DispatchTimeoutResult

semaphore.wait(timeout: DispatchTime.now() + 1)

作用:

  1. GCD控制并发数量的方法(NSOperation可以直接设置并发数)
  2. 信号量为 0: 等待, 型号量为1: 执行

应用:

1.做权限请求, 比如网络权限, 或者通讯获取权限等, 当用户同意网络权限之后信号量+1, 执行正常逻辑的代码

2.做网络请求的等待

3.控制并发数量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

func dispatchSemaphoreAsynNumberControl() -> Void {
    let group = DispatchGroup()
    //创建信号量, 起始值为5
    let semaphore = DispatchSemaphore(value: 5)
    let queue = DispatchQueue(label: "queue", qos: .default, attributes: .concurrent, autoreleaseFrequency: .inherit, target: nil)
    //创建50个任务, 没创建一个任务信号量减1, 创建5个后信号量为0, 阻塞当前线程, 并等待任务执行, 每个任务执行完成信号量加1, 线程阻塞被取消, 然后接着创建任务, 如此重复50次
    for i in 0 ..< 50 {
        semaphore.wait()//-
        let item = DispatchWorkItem(block: {
            sleep(5)
            print("item\(i)")
            semaphore.signal()//+
        })
        //单纯控制并发数量
//  queue.async(execute: item)
        //控制组的并发数量
        queue.async(group: group, execute: item)
    }
}

DispatchSourceTimer

注意:

  1. timer一定要强引用, 否则无效

  2. 不收UI界面滚动的影响
  3. 重复的时间间隔, 最小单位为纳秒,比Timer准确
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
 
	//声明全局变量timer
	var timer = DispatchSource.makeTimerSource(flags: .strict, queue: DispatchQueue.main)


	func dispatchTimer() -> Void {
  
    //       let timer = DispatchSource.makeTimerSource(flags: .strict, queue: DispatchQueue.main)
    //调用事件
    timer.setEventHandler {
        print("hhhh")
    }
    //取消定时器调用的时间
    timer.setCancelHandler {
        print("cancelled")
    }
    print(DispatchTime.now())
    //程序退到后台停止, repeating: 重复的时间间隔, 最小单位为纳秒,比Timer准确
	//timer.schedule(deadline: DispatchTime.now(), repeating: 1)
    
    //        timer.schedule(wallDeadline: DispatchWallTime.now(), repeating: 1)
    
    /**
     deadline:开始时间
     repeating: 间隔时间
     leeway: 控制误差经度
            DispatchTimeInterval.nanoseconds(100000)//纳秒
            DispatchTimeInterval.microseconds(1000)//微秒
            DispatchTimeInterval.milliseconds(1000)//毫秒
            DispatchTimeInterval.seconds(1)//秒
     */
    timer.schedule(deadline: DispatchTime.now() + 2.0, repeating: 1.0, leeway: DispatchTimeInterval.microseconds(5))
   
    //开始
    timer.resume()
    //暂停
    //        timer.suspend()
    //取消
    //        timer.cancel()
}

DispatchSource: 事件源

Dispatch Source是GCD中的一个基本类型,从字面意思可称为调度源,它的作用是当有一些特定的较底层的系统事件发生时,调度源会捕捉到这些事件,然后可以做其他的逻辑处理,调度源有多种类型,分别监听对应类型的系统事件。我们来看看它都有哪些类型:

  1. Timer Dispatch Source:定时调度源。

  2. Signal Dispatch Source:监听UNIX信号调度源,比如监听代表挂起指令的SIGSTOP信号。

  3. Descriptor Dispatch Source:监听文件相关操作和Socket相关操作的调度源。

  4. Process Dispatch Source:监听进程相关状态的调度源。

  5. Mach port Dispatch Source:监听Mach相关事件的调度源。

  6. Custom Dispatch Source:监听自定义事件的调度源。

用通俗一点的话说就是用GCD的函数指定一个希望监听的系统事件类型,再指定一个捕获到事件后进行逻辑处理的闭包或者函数作为回调函数,然后再指定一个该回调函数执行的Dispatch Queue即可,当监听到指定的系统事件发生时会调用回调函数,将该回调函数作为一个任务放入指定的队列中执行。也就是说当监听到系统事件后就会触发一个任务,并自动将其加入队列执行,这里与之前手动添加任务的模式不同,一旦将Diaptach Source与Dispatch Queue关联后,只要监听到系统事件,Dispatch Source就会自动将任务(回调函数)添加到关联的队列中。有些时候回调函数执行的时间较长,在这段时间内Dispatch Source又监听到多个系统事件,理论上就会形成事件积压,但好在Dispatch Source有很好的机制解决这个问题,当有多个事件积压时会根据事件类型,将它们进行关联和结合,形成一个新的事件。

监听事件类型

Dispatch Source一共可以监听六类事件,分为11个类型,我们来看看都是什么:

  1. DISPATCH_SOURCE_TYPE_DATA_ADD:属于自定义事件,可以通过dispatch_source_get_data函数获取事件变量数据,在我们自定义的方法中可以调用dispatch_source_merge_data函数向Dispatch Source设置数据,下文中会有详细的演示。

  2. DISPATCH_SOURCE_TYPE_DATA_OR:属于自定义事件,用法同上面的类型一样。
  3. DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口发送事件。
  4. DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。
  5. DISPATCH_SOURCE_TYPE_PROC:与进程相关的事件。
  6. DISPATCH_SOURCE_TYPE_READ:读文件事件。
  7. DISPATCH_SOURCE_TYPE_WRITE:写文件事件。
  8. DISPATCH_SOURCE_TYPE_VNODE:文件属性更改事件。
  9. DISPATCH_SOURCE_TYPE_SIGNAL:接收信号事件。
  10. DISPATCH_SOURCE_TYPE_TIMER:定时器事件。
  11. 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:第四个参数设置回调函数所在的队列。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	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设置处理器,前者是设置闭包形式的处理器,后者是设置函数形式的处理器:

```c
dispatch_source_set_event_handler(dispatchSource, {              
     print("Dispatch Source 事件处理器...")})// 根据闭包尾随的特性,还可以有下面的写法dispatch_source_set_event_handler(dispatchSource) {
    print("Dispatch Source 事件处理器...")     
}
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
__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,这三个函数返回数据的值和类型都会不一样,下面来看看这三个函数:

  1. dispatch_source_get_handle:这个函数用于获取在创建Dispatch Source时设置的第二个参数handle。
    • 如果是读写文件的Dispatch Source,返回的就是描述符。

    • 如果是信号类型的Dispatch Source,返回的是int类型的信号数。
    • 如果是进程类型的Dispatch Source,返回的是pid_t类型的进程id。
    • 如果是Mach端口类型的Dispatch Source,返回的是mach_port_t类型的Mach端口。
  2. 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函数设置的数据。

  3. 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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
dispatch_source_set_cancel_handler(dispatchSource) {                
print("进行善后处理...")      
}
``` 

该函数有两个参数,第一个参数是目标Dispatch Source,第二个参数就是要进行善后处理的闭包或者函数。

 

##### 更改Dispatch Source的目标队列

在上文中,我们说过可以使用dispatch_source_create函数创建Dispatch Source,并且在创建时会指定回调函数执行的队列,那么如果事后想更改队列,比如说想更改队列的优先级,这时我们可以使用dispatch_set_target_queue函数实现:swift

```c
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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_TIMERDispatch Source设置相关属性,该函数有四个参数:

1. source:该参数为目标Dispatch Source,类型为dispatch_source_t.


2. start:该参数为定时器的起始时间,类型为dispatch_time_t
3. interval:该参数为定时器的间隔时间,类型为UInt64,间隔时间的单位是纳秒。
4. leeway:该参数为间隔时间的精度,类型为UInt64,时间单位也是纳秒。


##### 用Dispatch Source监听自定义事件

Dispatch Source能监听的事件中有一个类型是自定义事件,下面我们来看看如何使用:

 
```c

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。