每当与底层系统交互时,你都必须为该任务花费大量时间做好准备。调用内核层或其他系统层涉及到上下文的更改,与在你自己的进程中发生的调用相比,该更改相当昂贵。因此,许多系统库提供异步接口,允许你的代码向系统提交请求,并在处理该请求的同时继续执行其他工作。Grand Central Dispatch
基于此一般行为,允许你提交请求,并使用blocks
和dispatch queues
将结果报告回代码。
Dispatch Sources
dispatch source
是一种基本数据类型,用于协调特定低级系统事件的处理。Grand Central Dispatch
支持以下类型的dispatch sources
:
Timer dispatch sources
生成定期通知。当
UNIX
信号到达时,Signal dispatch sources
(信号调度源)会通知你。descriptor sources
会通知你各种基于文件和套接字的操作,例如:
1 | 1、当数据可供读取时 |
Process dispatch sources
通知你与进程相关的事件,例如:
1 | 1、进程退出时 |
Mach port dispatch sources
会通知你与Mach
相关的事件。Custom dispatch sources
是你自己定义并触发的源。
Dispatch sources
取代了通常用于处理与系统相关事件的异步回调函数。配置dispatch source
时,可以指定 要监视的事件 以及 用于处理这些事件的dispatch queue
和代码。可以使用block
对象或函数指定代码。当感兴趣的事件到达时,dispatch source
将你的block
或函数提交到指定的dispatch queue
执行。
与手动提交到queue
的任务不同,dispatch sources
为你的应用程序提供了连续的事件源。dispatch source
将一直附加到其dispatch queue
,直到你明确取消它为止。附加时,每当发生相应事件时,它都会将其关联的任务代码提交给dispatch queue
。某些事件(例如timer events
)以固定的间隔发生,但大多数事件仅在特定条件出现时才偶尔发生。因此,dispatch sources``retain
其关联的dispatch queue
,以防止在事件可能仍处于挂起状态时过早地释放该queue
。
为了防止事件在dispatch queue
中积压,dispatch sources
实现了event coalescing scheme
(事件合并方案)。如果新事件在 前一个事件的event handler
出queue
并执行 之前到达,则dispatch source
将新事件中的数据与旧事件中的数据合并。根据事件的类型,合并可能会替换旧事件或更新其持有的信息。例如,signal-based dispatch source
(基于信号的调度源)仅提供有关最新信号的信息,而且还报告自从上次调用event handler
(事件处理程序)以来已传递了多少信号总数。
创建 Dispatch Sources
创建dispatch source
包括创建 事件源 和dispatch source
本身。事件的 source
是处理事件所需的任何本地数据结构。例如,对于descriptor-based dispatch source
(基于描述符的调度源),你将需要打开描述符;对于process-based source
(基于进程的源),则需要获取目标程序的进程ID。有了event source
(事件源)后,就可以按照以下方式创建相应的dispatch source
:
1、使用 dispatch_source_create
函数创建dispatch source
2、配置dispatch source
:
- 将
event handler
分配给dispatch source
; - 对于
timer sources
,请使用dispatch_source_set_timer
函数设置定时器信息。
3、为dispatch source
分配一个cancellation handler
(取消处理程序) (这一步可选);
4、调用dispatch_resume
函数开始处理事件;
由于 dispatch sources
在使用前需要进行一些额外配置,因此dispatch_source_create
函数以挂起状态返回 dispatch sources
。挂起时,dispatch source
将接收事件,但不处理它们。这使你有时间安装 event handler
并执行处理实际事件所需的任何其他配置。
编写和安装 Event Handler
要处理 dispatch source
生成的事件,必须定义一个event handle
(事件处理程序)来处理这些事件。event handle
是你使用dispatch_source_set_event_handler
或dispatch_source_set_event_handler_f
函数安装在 dispatch source
上的函数或block
对象。当事件到达时,dispatch source
将你的event handler
提交到指定的dispatch queue
进行处理。
event handler
的主体负责处理所有到达的事件。如果你的 event handler
已经排队并且正在等待处理事件,当一个新事件到来时,则dispatch source
将合并这两个事件。event handler
通常只查看最新事件的信息,但根据dispatch source
的类型,event handler
也可以能够获取有关发生并合并的其他事件的信息。如果一个或多个新事件在event handler
开始执行后到达,则 dispatch source
将保留这些事件,直到当前event handler
完成执行为止。此时,它将使用新事件再次将event handler
提交到队列。
1 | // 基于 block 的事件处理程序不带参数,也没有返回值。 |
在event handler
内部,可以从dispatch source
本身获取有关给定事件的信息。尽管将function-based event handlers
(基于函数的事件处理程序)作为参数传递了指向dispatch source
的指针,但是block-based event handlers
(基于块的事件处理程序)必须自己捕获该指针。可以通过正常引用包含dispatch source
的变量来对blocks
执行此操作。 例如,以下代码片段捕获了在blocks
范围之外声明的source
变量。
1 | dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, |
在block
内部捕获变量通常是为了获得更大的 flexibility
(灵活性) 和 dynamism
(动态性)。当然,默认情况下,捕获的变量在block
内是只读的。尽管blocks
功能支持了在特定情况下修改捕获的变量,但不应在与dispatch source
关联的event handlers
中尝试这样做。Dispatch sources
始终异步执行其event handlers
,因此捕获的任何变量的定义范围可能在event handler
执行时就消失了。
如下列出了可以从event handler
代码中调用以获取事件信息的函数。
dispatch_source_get_handle
此函数返回dispatch source
管理的基础系统数据类型。
对于descriptor dispatch source
,此函数返回一个int
类型,其中包含与dispatch source
关联的descriptor
(描述符)。
对于signal dispatch source
,此函数返回一个int
类型,其中包含最近事件的信号编号。
对于process dispatch source
,此函数返回被监视进程的pid_t
数据结构。
对于Mach port dispatch source
,此函数返回mach_port_t
数据结构。
对于其他dispatch sources
,此函数返回的值未定义。
dispatch_source_get_data
此函数返回与事件关联的所有挂起数据。
对于从文件读取数据的descriptor dispatch source
,此函数返回可读取的字节数。
对于将数据写入文件的descriptor dispatch source
,如果有可用的写入空间,则此函数返回正整数。
对于监视文件系统活动的descriptor dispatch source
,此函数返回一个常量,指示发生的事件的类型。有关常量的列表,请参见dispatch_source_vnode_flags_t
枚举类型。
对于process dispatch source
,此函数返回一个常量,指示发生的事件的类型。有关常量的列表,请参见dispatch_source_proc_flags_t
枚举类型。
对于Mach port dispatch source
,此函数返回一个常量,指示发生的事件的类型。有关常量的列表,请参见dispatch_source_machport_flags_t
枚举类型。
对于custom dispatch source
,此函数返回根据现有数据创建的新数据值,并将新数据传递给dispatch_source_merge_data
函数。
dispatch_source_get_mask
此函数返回用于创建dispatch source
的事件标志。
对于process dispatch source
,此函数返回dispatch source
接收的事件的掩码。有关常量的列表,请参见dispatch_source_proc_flags_t
枚举类型。
对于具有发送权限的Mach port dispatch source
,此函数返回所需事件的掩码。 有关常量的列表,请参见dispatch_source_mach_send_flags_t
枚举类型。
对于自定义OR dispatch source
,此函数返回用于合并数据值的掩码。
安装 Cancellation Handler
Cancellation handlers
(取消处理程序)用于在release
dispatch source
之前对其进行清理。对于大多数类型的dispatch sources
,cancellation handlers
是可选的,并且仅当你有一些与dispatch source
相关联的自定义行为也需要更新时,才需要Cancellation handlers
。但是,对于使用descriptor
或 Mach port
的dispatch sources
,必须提供cancellation handler
以关闭descriptor
或release
Mach port
。否则,由于代码或系统其他部分无意识地重用了那些结构,可能导致代码中的细微错误。
你可以随时安装cancellation handler
,但通常在创建dispatch source
时会这样做。你可以使用dispatch_source_set_cancel_handler
或dispatch_source_set_cancel_handler_f
函数来安装cancellation handler
,具体取决于你要在实现中使用block
对象还是函数。
下面的示例显示一个简单的cancellation handler
,它关闭为dispatch source
打开的descriptor
(描述符)。fd
变量是包含descriptor
的捕获变量。
1 | dispatch_source_set_cancel_handler(mySource, ^{ |
更改Target Queue
尽管在创建dispatch source
时指定了要在其上运行事件和cancellation handlers
(取消处理程序)的队列,但可以使用dispatch_set_target_queue
函数随时更改该queue
。你可以这样做来更改处理dispatch source
事件的优先级。
更改dispatch source
的queue
是异步操作,dispatch source
会尽最大努力尽快进行更改。如果event handler
(事件处理程序)已经在队列中并等待处理,它将在前一个queue
上执行。但是,在你进行更改时 到达的其它事件 可以在任一queue
中进行处理。
将自定义数据 与 Dispatch Source 相关联
与Grand Central Dispatch
中的许多其他数据类型一样,你可以使用dispatch_set_context
函数将自定义数据与dispatch source
相关联。可以使用上下文指针存储event handler
处理事件所需的任何数据。如果确实在上下文指针中存储了任何自定义数据,则还应该安装cancellation handler
,以便在不再需要dispatch source
时release
该数据。
如果使用blocks
实现event handler
(事件处理程序),则还可以捕获局部变量并在block-based code
(基于块的代码)中使用它们。尽管这可以减少将数据存储在dispatch source
的上下文指针中的需要,但你应始终谨慎使用此功能。因为dispatch sources
可能在你的应用程序中长期存在,所以在捕获包含指针的变量时应格外小心。如果可以随时释放指针所指向的数据,则应复制数据或保留数据以防止发生这种情况。无论哪种情况,你都需要提供一个cancellation handler
,以便以后释放数据。
Dispatch Sources 的内存管理
像其他dispatch objects
一样,dispatch sources
也是reference counted data types
(引用计数的数据类型)。dispatch source
的初始引用计数为1,可以使用dispatch_retain
和dispatch_release
函数进行保留和释放。当queue
的引用计数达到零时,系统将自动释放dispatch source
数据结构。
由于它们的使用方式,可以在dispatch sources
本身内部或外部管理dispatch sources
的所有权。有了外部所有权,另一个对象或代码段就拥有了dispatch source
的所有权,并负责在不再需要时释放它。对于内部所有权,dispatch source
拥有自己,并负责在适当的时间释放自己。尽管外部所有权很常见,但是在想要创建自主dispatch source
,并让它管理代码的某些行为 而无需任何进一步交互的情况下,可以使用 内部所有权。例如,如果dispatch source
被设计为响应单个全局事件,则可以让它处理该事件然后立即退出。
Dispatch Source 示例
以下各节说明如何创建和配置一些更常用的调度源。
创建 Timer
Timer dispatch sources
以定期、基于时间的时间间隔生成事件。可以使用timer
来启动需要定期执行的特定任务。例如,游戏和其他图形密集型应用程序可能使用计时器来启动屏幕或动画更新。还可以设置一个timer
,并使用产生的事件来检查频繁更新的服务器上的新信息。
所有timer dispatch sources
都是interval timers
(间隔计时器),即一旦创建,它们就会以你指定的间隔传递常规事件。创建timer dispatch source
时,必须指定的值之一是leeway
(回旋值),以使系统对timer events
的期望精度有所了解。Leeway
值使系统在 管理电源 和 唤醒内核 方面具有一定的灵活性。例如,系统可以使用leeway
值来提前或延迟触发时间,并使它与其他系统事件更好地保持一致。因此,你应该尽可能为自己的timers
指定一个leeway
值。
注意:即使你指定的
leeway
值为0,也永远不要期望timer
以你要求的精确纳秒触发。该系统会尽力满足您你需求,但无法保证确切的触发时间。
当计算机进入sleep
状态时,所有timer dispatch sources
都将被挂起。当计算机唤醒时,这些timer dispatch sources
也会自动唤醒。根据timer
的配置,这种性质的暂停可能会影响你的timer
下次触发的时间。如果你使用dispatch_time
函数或DISPATCH_TIME_NOW
常量设置timer dispatch source
,则timer dispatch source
将使用default system clock
(默认系统时钟)来确定何时触发。但是,当计算机处于休眠状态时,default clock
不会提前。相比之下,当使用dispatch_walltime
函数设置timer dispatch source
时,timer dispatch source
会将其触发时间跟踪到挂钟时间。后一种选项通常适用于触发间隔相对较大的timers
,因为它可以防止事件时间之间的偏移过大。
创建一个timer dispatch source
:
1 | // 或者在主队列 dispatch_get_main_queue() |
尽管创建timer dispatch source
是接收基于时间的事件的主要方式,但也可以使用其他选项。如果要在指定的时间间隔后执行一次block
,则可以使用dispatch_after
或dispatch_after_f
函数。该函数的行为与dispatch_async
函数非常相似,不同之处在于,它允许你指定将block
交到queue
的时间值。可以根据需要将时间值指定为相对或绝对时间值。
1 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ |
从Descriptor
读取数据
要从file
(文件)或socket
(套接字)读取数据,必须打开file
(文件)或socket
(套接字)并创建DISPATCH_SOURCE_TYPE_READ
类型的dispatch source
。你指定的event handler
应该能够读取和处理file descriptor
(文件描述符)的内容。对于file
,这相当于读取文件数据(或该数据的子集)并为你的应用程序创建适当的数据结构。对于network socket
(网络套接字),这涉及处理新接收到的网络数据。
每当读取数据时,都应始终将descriptor
(描述符)配置为使用non-blocking operations
(非阻塞操作)。虽然可以使用dispatch_source_get_data
函数查看有多少数据可供读取,但是该函数返回的数字可能会在你进行调用的时间 与 你实际读取数据的时间之间发生变化。如果基础文件被截断或发生网络错误,则从当前线程中的descriptor
(描述符)中读取数据可能会使event handler
在执行过程中暂停,并阻止dispatch queue
调度其他任务。对于serial queue
,这可能会使队列死锁,甚至对于concurrent queue
,这也会减少可以启动的新任务的数量。
如下示例,将dispatch source
配置为从文件读取数据:
1 | dispatch_source_t ProcessContentsOfFile(const char* filename) |
将数据写入 Descriptor
将数据写入file
(文件)或socket
(套接字)的过程与读取数据的过程非常相似。配置用于写操作的descriptor
(描述符)之后,将创建类型为DISPATCH_SOURCE_TYPE_WRITE
的dispatch source
。一旦创建dispatch source
,系统将调用你的event handler
,使它有机会开始将数据写入file
(文件)或socket
(套接字)。完成数据写入后,请使用dispatch_source_cancel
函数取消dispatch source
。
每当写入数据时,都应始终将file descriptor
(文件描述符)配置为使用non-blocking operations
(非阻塞操作)。尽管可以使用dispatch_source_get_data
函数查看有多少空间可供写入,但是该函数返回的值仅是建议性的,并且可能在 调用时间 和 实际写入数据的时间 之间发生变化。如果发生错误,将数据写入block
的file descriptor
可能会使event handler
执行过程中暂停,并阻止dispatch queue
调度其他任务。对于serial queue
,这可能会使队列死锁,甚至对于concurrent queue
,这也会减少可以启动的新任务的数量。
监控 File-System 对象
如果要监控file system object
(文件系统对象)的更改,则可以设置类型为DISPATCH_SOURCE_TYPE_VNODE
的dispatch source
。当文件被删除,写入或重命名时,可以使用这种类型的dispatch source
来接收通知。当文件的特定类型的元信息(例如文件的大小和链接数)发生变化时,你也可以使用它来发出警报。
注意:当
source
本身正在处理事件时,为dispatch source
指定的file descriptor
(文件描述符)必须保持打开状态。
监控信号
UNIX signals
允许从其域外部对应用程序进行操作。应用程序可以接收许多不同类型的signals
(信号),范围从不可恢复的错误(例如非法指令)到有关重要信息的通知(例如子进程退出时)。传统上,应用程序使用sigaction
函数来安装signal handler function
,该功能在signals
到达时立即对signals
同步处理。如果你只想收到signals
到达的通知,而实际上却不想处理该signals
,则可以使用signal dispatch source
来异步处理signals
。
Signal dispatch sources
(信号调度源)不能替代你使用sigaction
函数安装的synchronous signal handlers
(同步信号处理程序)。Synchronous signal handlers
实际上可以捕获signal
(信号)并阻止它终止你的应用程序。Signal dispatch sources
允许你仅监视signal
(信号)的到达。此外,你不能使用signal dispatch sources
来检索所有类型的signal
(信号)。具体来说,不能使用它们来监视SIGILL
,SIGBUS
和SIGSEGV
信号。
因为signal dispatch sources
(信号调度源)在dispatch queue
上异步执行,所以它们不受与synchronous signal handlers
相同的限制。例如,你可以从signal dispatch source
的事件处理程序中调用的函数没有任何限制。这种灵活性的权衡是,在signal
到达的时间与dispatch source
的事件处理程序被调用之间的延迟可能会有所增加。
如果你正在为自定义框架开发代码,则使用signal dispatch sources
的优点是你的代码可以独立于与其链接的任何应用程序监控信号。Signal dispatch sources
不会干扰应用程序可能已安装的其他dispatch sources
或任何同步信号处理程序。
监控进程
process dispatch source
使你可以监视特定进程的行为并做出适当响应。父进程可以使用这种类型的dispatch source
来监视它创建的任何子进程。例如,父进程可以使用它来监视子进程的死亡。同样,子进程可以使用它来监视其父进程并在父进程退出时退出。
清单4-6显示了安装调度源以监视父进程终止的步骤。 当父进程死亡时,调度源将设置一些内部状态信息,以使子进程知道应该退出。 (您自己的应用程序将需要实现MySetAppExitFlag函数,以设置适当的终止标志。)由于调度源是自动运行的,因此拥有自己的资源,因此它在预期程序关闭时也会取消并释放自身。
取消 Dispatch Source
Dispatch sources
将保持活动状态,直到你使用 dispatch_source_cancel
函数显式取消它为止。取消dispatch source
将停止新事件的传递,并且无法撤消。 因此,通常可以取消dispatch source
,然后立即释放它,如下所示:
1 | void RemoveDispatchSource(dispatch_source_t mySource) |
dispatch source
是异步操作。尽管在调用dispatch_source_cancel
函数之后不会处理任何新事件,但是已由dispatch source
处理的事件将继续被处理。完成所有最终事件的处理后,如果存在dispatch source
,则dispatch source
将执行其cancellation handler
(取消处理程序)。
cancellation handler
(取消处理程序)是你 释放内存 或 清除代表dispatch source
获取的任何资源的机会。如果你的dispatch source
使用descriptor
(描述符)或mach port
,则必须提供cancellation handler
(取消处理程序)以关闭descriptor
(描述符)或在发生取消时销毁port
。其他类型的dispatch sources
不需要cancellation handlers
,但是如果将任何内存或数据与dispatch source
相关联,则仍应提供cancellation handlers
。例如,如果将数据存储在dispatch source
的上下文指针中,则应提供一个。
挂起和恢复 Dispatch Sources
可以使用dispatch_suspend
和dispatch_resume
方法临时挂起和恢复dispatch source
事件的传递。这些方法增加和减少dispatch object
的挂起计数。因此,在恢复事件传递之前,必须平衡对dispatch_suspend
的每个调用和匹配对dispatch_resume
的调用。
当你挂起dispatch source
时,该dispatch source
被挂起时发生的任何事件都将被累积,直到恢复queue
为止。当queue
恢复时,不是传递所有事件,而是在传递之前将事件合并为单个事件。例如,如果您正在监视文件中的名称更改,则传递的事件将仅包括姓氏更改。以这种方式合并事件可以防止它们在队列中堆积,并在工作恢复时使您的应用程序不堪重负。