dispatch source和runLoop source都是用来监听事件的,你可以创建不同类型的dispatch source对象和runLoop source对象。dispatch source监测到事件产生时,会将事件处理器添加到dispatch queue ,稍后出队执行;runLoop source对象一般是NSObject对象,需要先按照某种模式加入到指定线程的runLoop中,利用runLoop这个循环来检测符合模式的事件是否产生,如果产生则向NSObject对象或其代理发送消息。dispatch source和runLoop source都是异步处理模式,你只要创建、设置好对象,就可以继续做其他事了。
Grand Central Dispatch支持以下类型的dispatch source:
Timer dispatch source:监测定期产生的事件
Signal dispatch source:监测UNIX信号到达的事件
Descriptor dispatch source:监测各种文件和socket科操作的事件:数据可读,数据可写
Filesystem node event source:监测文件在文件系统中被删除、移动、重命名、文件元数据信息改变的事件
Process dispatch source:监测进程相关的事件:当进程退出时,当进程发起fork或exec等调用,信号被递送到进程
Mach port dispatch source:监测Mach相关端口事件
Custom dispatch source:监测自己定义事件(需要自己触发该事件)
这里要理解“监测”的含义,我们都知道:在面向对象的编程世界里,对象不会主动监测某个事件,往往是被通知发生什么情况了或满足什么条件了,发生什么情况了或满足什么条件了就是”事件“产生了,哪个对象需要处理该事件,就要立即通知他,告诉他事件产生了。站在被通知者角度,说成监测某事件,便于理解。
创建Dispatch Source
使用 dispatch_source_create 函数创建dispatch source。当你创建一个dispatch source时,需要指定派发源类型(其实也就是监测的事件类型)、事件源(事件源是产生事件的本体,是一个底层系统数据类型的对象,例如基于描述符的dispatch source,需要传入打开的描述符;基于进程的dispatch source,需要传入目标程序的进程ID。)、事件源详情(一般用来描述具体事件)、dispatch queue、以及处理事件的代码(block或函数)。当事件发生时,dispatch source会提交你的block或函数到指定的queue去执行。和手工提交到queue的任务不同,dispatch source为应用提供连续的事件响应。除非你显式地取消,dispatch source会一直保留与dispatch queue的关联。只要相应的事件发生,就会提交关联的代码到dispatch queue去执行。
为了防止事件积压到dispatch queue,dispatch source实现了事件合并机制。如果新事件在上一个事件处理器出列并执行之前到达,dispatch source会将新旧事件的数据合并。根据事件类型的不同,合并操作可能会替换旧事件,或者更新旧事件的信息。
配置dispatch source
为dispatch source设置一个事件处理器(对于定时器源,还需要使用 dispatch_source_set_timer 函数设置定时器信息)
为dispatch source赋予一个取消处理器(可选)调用 dispatch_resume 函数开始处理事件由于dispatch source必须进行额外的配置才能被使用,dispatch_source_create 函数返回的dispatch source将处于挂起状态。此时dispatch source会接收事件,但是不会进行处理。这时候你可以安装事件处理器,并执行额外的配置。
你需要定义一个事件处理器来处理事件,可以是函数或block对象,并使用 dispatch_source_set_event_handler 或 dispatch_source_set_event_handler_f 设置事件处理器。事件到达时,dispatch source会提交你的事件处理器到指定的dispatch queue,由queue执行事件处理器。
事件处理器的代码负责处理所有到达的事件。如果事件处理器已经在queue中并等待出队执行,此时又来了一个新事件,dispatch source会合并这两个事件。事件处理器通常只能看到最新事件的信息,不过某些类型的dispatch source也能获得已经发生以及合并的事件信息。
如果事件处理器已经开始执行,一个或多个新事件到达,dispatch source会保留这些事件,直到前面的事件处理器完成执行。然后以新事件再次提交处理器到queue。
函数事件处理器有一个void*指针指向你使用dispatch_set_context方法设置的对象,没有返回值。Block事件处理器没有参数,也没有返回值。
// Block-based event handler
void (^dispatch_block_t)(void)
// Function-based event handler
void (*dispatch_function_t)(void *)
在事件处理器中,你可以从dispatch source中获得事件的信息,函数处理器可以直接使用参数指针,Block则必须自己捕获到dispatch source指针,一般block定义时会自动捕获到外部定义的所有变量。
dispatch_source_t mySource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
myDescriptor, 0, myQueue);
dispatch_source_set_event_handler(mySource, ^{
// Get some data from the source variable, which is captured
// from the parent context.
size_t estimated = dispatch_source_get_data(mySource);
// Continue reading the descriptor...
});
dispatch_resume(mySource);
Block捕获外部变量允许更大的灵活性和动态性。当然,在Block中这些变量默认是只读的,虽然可以使用__block来修改捕获的变量,但是你最好不要在事件处理器中这样做。因为Dispatch source异步执行事件处理器,当事件处理器修改原始外部变量时,有可能这些变量已经不存在了。
在事件处理器中可以使用如下函数获取需要的信息:
dispatch_source_get_handle
这个函数返回dispatch source管理的底层系统数据类型,就是dispatch_source_create函数中第二个参数
对于描述符dispatch source,函数返回一个int,表示关联的描述符
对于信号dispatch source,函数返回一个int,表示最新事件的信号数值
对于进程dispatch source,函数返回一个pid_t数据结构,表示被监控的进程
对于Mach port dispatch source,函数返回一个 mach_port_t 数据结构
dispatch_source_get_data
这个函数返回值,取决于派发源类型
对于从文件中读取数据的描述符dispatch source,这个函数返回可以读取的字节数
对于向文件中写入数据的描述符dispatch source,如果可以写入,则返回正数值
对于监控文件系统活动的描述符dispatch source,函数返回一个常量,表示发生的事件类型,参考 dispatch_source_vnode_flags_t 枚举类型
对于进程dispatch source,函数返回一个常量,表示发生的事件类型,参考 dispatch_source_proc_flags_t 枚举类型
对于Mach port dispatch source,函数返回一个常量,表示发生的事件类型,参考 dispatch_source_machport_flags_t 枚举类型
对于自定义dispatch source,函数返回从现有数据创建的新数据(前面提到的事件合并),以及传递给 dispatch_source_merge_data 函数的新数据。
dispatch_source_get_mask
这个函数返回用来创建dispatch source的事件标志,就是dispatch_source_create函数中第三个参数
对于进程dispatch source,函数返回dispatch source接收到的事件掩码,参考 dispatch_source_proc_flags_t 枚举类型
对于发送权利的Mach port dispatch source,函数返回期望事件的掩码,参考 dispatch_source_mach_send_flags_t 枚举类型
对于自定义 “或” 的dispatch source,函数返回用来合并数据值的掩码
设置取消处理器
取消处理器让你在dispatch soruce释放之前执行清理工作。多数类型的dispatch source不需要取消处理器,除非你对dispatch source有自定义行为需要在释放时执行。但是使用描述符或Mach port的dispatch source必须设置取消处理器,用来关闭描述符或释放Mach port。否则可能导致微妙的bug,这些结构体会被系统其它部分或你的应用在不经意间重用。你可以在任何时候设置取消处理器,但通常我们在创建dispatch source时就会设置取消处理器。使用 dispatch_source_set_cancel_handler 或 dispatch_source_set_cancel_handler_f 函数来设置取消处理器。
列如,下面设置”文件描述符派发源“的取消处理器:
dispatch_source_set_cancel_handler(mySource, ^{
close(myDescriptor); // Close a file descriptor opened earlier.
});
取消一个Dispatch Source
除非你显式地调用 dispatch_source_cancel 函数,否则dispatch source将一直保持活动;取消一个dispatch source只会停止将事件处理器入队,派发源依旧在监听事件,并且不能撤销。因此你通常在取消dispatch source后立即释放它。取消一个dispatch source是异步操作,调用 dispatch_source_cancel 之后,不会再有新的事件被处理,但是正在被dispatch source处理的事件会继续被处理完成。在处理完最后的事件之后,dispatch source会执行自己的取消处理器。
取消处理器是你最后的执行机会,在那里执行内存或资源的释放工作。例如描述符或mach port类型的dispatch source,必须提供取消处理器,用来关闭描述符或mach port
挂起和继续Dispatch Source
你可以使用 dispatch_suspend 和 dispatch_resume 临时地挂起和继续dispatch source的事件递送。这两个函数分别增加和减少dispatch 对象的挂起计数。因此,你必须每次 dispatch_suspend 调用之后,都需要相应的 dispatch_resume 才能继续事件递送。
挂起一个dispatch source期间,发生的任何事件都会被累积,直到dispatch source继续。但是不会递送所有事件,而是先合并到单一事件,然后再一次递送。例如你监控一个文件的文件名变化,就只会递送最后一次的变化事件。
修改目标Queue
在创建dispatch source时可以指定一个queue,用来执行事件处理器和取消处理器。不过你也可以使用 dispatch_set_target_queue 函数在任何时候修改目标queue。修改queue可以改变执行dispatch source事件的优先级。
修改dispatch source的目标queue是异步操作,dispatch source会尽可能快地完成这个修改。如果事件处理器已经进入queue并等待处理,它会继续在原来的Queue中执行。随后到达的所有事件的处理器都会在后面修改的queue中执行。
关联自定义数据到dispatch source
和Grand Central Dispatch的其它类型一样,你可以使用 dispatch_set_context 函数关联自定义数据到dispatch source。使用context指针存储事件处理器需要的任何数据。如果你在context指针中存储了数据,你就应该安装一个取消处理器,在dispatch source不再需要时释放这些context自定义数据。
如果你使用block实现事件处理器,你也可以捕获本地变量,并在Block中使用。虽然这样也可以代替context指针,但是你应该明智地使用Block捕获变量。因为dispatch source长时间存在于应用中,Block捕获指针变量时必须非常小心,因为指针指向的数据可能会被释放,因此需要复制数据或retain。不管使用哪种方法,你都应该提供一个取消处理器,在最后释放这些数据。
Dispatch Source的内存管理
Dispatch Source也是引用计数的数据类型,初始计数为1,可以使用 dispatch_retain 和 dispatch_release 函数来增加和减少引用计数。引用计数到达0时,系统自动释放dispatch source数据结构。
dispatch source的所有权可以由dispatch source内部或外部进行管理。外部所有权时,另一个对象拥有dispatch source,并负责在不需要时释放它。内部所有权时,dispatch source自己拥有自己,并负责在适当的时候释放自己。虽然外部所有权很常用,当你希望创建自主dispatch source,并让它自己管理自己的行为时,可以使用内部所有权。例如dispatch source应用单一全局事件时,可以让它自己处理该事件,并立即退出。