分分钟学会GCD
2014
什么是GCD
Grand Central Dispatch (GCD)是异步运行任务的技术之中的一个。一般将应用程序中记述的线程管理用的代码在系统级中实现。因为线程管理是作为系统的一部分来实现的,因此可统一管理,也可运行任务,这样就比曾经的线程更有效率。
也就是说,GCD用我们难以置信的很简洁的记述方法。实现了极为复杂的多线程编程。
dispatch_async(queue, ^{ // 长时间处理 // 比如数据库訪问 // 比如图像识别 // 长时间处理结束,主线程使用该处理结果 dispatch_async(dispatch_get_main_queue(), ^{ // 仅仅在主线程能够运行的处理 // 比如界面更新 }) })
上面的就是在后台线程中运行长时间处理,处理结束时。主线程使用该处理结果。
多线程编程
尽管CPU的相关技术有非常多,但基本上1个CPU内核一次可以运行的CPU命令始终为1。那么如何才干在多条路径中运行CPU命令列呢?
OX X 和 ios 的核心 XNU 内核在发生操作系统事件时(如每隔一定时间,唤起系统调用等情况)会切换路径。运行中路径的状态,比如CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续运行切换路径CPU命令列。这被称为“上下文切换”。
因为使用多线程的程序可以在某个线程和其它线程之间重复多次进行上下文切换,因此看上去就好像1个CPU内核可以并列地运行多个线程一样。
可是,多线程编程实际上是一种易发生各种问题的编程技术。比方多个线程更新同样的资源会导致数据的不一致,停止等待时间的线程会导致多个线程互相持续等待。使用太多线程会消耗大量内存等。
应用程序在启动时,通过最先运行的线程,即主进程来描绘用户界面,处理触摸屏幕的事件等。假设在该主线程中进行长时间的处理,会妨碍主进程的运行。这就是长时间的处理不在主线程中运行而在其它线程中运行的原因。
GCD 的 API
苹果官方对 GCD 的说明。
开发人员要做的仅仅是定义运行的任务并追加到适当地 Dispatch Queue 中。
dispatch_async(queue, ^{ // 想运行的任务 });通过 dispatch_async 函数追加赋值在变量 queue 的 Dispatch Queue 中。
仅这样就能够使指定的 Block 在还有一线程中运行。
Dispatch Queue 是什么呢?如其名称所看到的,是运行处理的等待队列。应用程序编程人员通过 dispatch_async 函数等 API, 在 Block 语法中记述想运行的处理并将其追加到 Dispatch Queue 中。依照追加的顺序,先进先出运行处理。
另外在运行处理时存在两种 Dispatch Queue,一种是等待如今运行中处理的 Serial Dispatch Queue,还有一种是不等待如今运行中处理的Concurrent Dispatch Queue。
那么怎样才干得到这些 Dispatch Queue 呢?方法有两种。
dispatch_queue_create
第一种方法是通过 GCD 的 API 生成 Dispatch Queue。
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.mySerialDispatchQueue", NULL);在说明 dispatch_queue_create 函数之前。先讲一下关于 Serial Dispatch Queue 生成个数的注意事项。
如前所诉。Concurrent Dispatch Queue 并行运行多个追加处理,而 Serial Dispatch Queue 同一时候仅仅能运行1个追加处理。 尽管 Serial Dispatch Queue 和 Concurrent Dispatch Queue 受到系统资源的限制,但用 dispatch_queue_create 函数可生成随意多个 Dispatch Queue。
当生成多个 Serial Dispatch Queue 时,各个 Serial Dispatch Queue 将并行运行。尽管在1个 Serial Dispatch Queue 中同一时候仅仅能运行一个追加处理。但假设将处理分别追加到4个 Serial Dispatch Queue 中,各个 Serial Dispatch Queue 运行1个,即为同一时候运行4个处理。
一旦生成 Serial Dispatch Queue 并追加处理,系统对于一个 Serial Dispatch Queue 就仅仅生成并使用一个线程。假设生成 2000 个 Serial Dispatch Queue,那么就生成 2000 个线程。
像之前列举的多线程编程问题一样。假设过多使用多线程。就会消耗大量内存,引起大量的上下文切换,大幅度减少系统的响应性能。
以下我们回来继续讲 dispatch_queue_create 函数。
该函数的第一个參数指定 Serial Dispatch Queue 的名称。
假设嫌麻烦设为 NULL 也能够。但你在调试中一定会懊悔没有为 Serial Dispatch Queue 署名。
生成 Serial Dispatch Queue 时,像该源码这样,将第二个參数指定为NULL。生成 Concurrent Dispatch Queue 时。指定为 DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);dispatch_queue_create 函数的返回值为表示 Dispatch Queue 的 dispatch_queue_t 类型。
在之前源码中所出现的变量 queue 均为 dispatch_queue_t 类型变量。
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd.MyConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(myConcurrentDispatchQueue, ^{ NSLog(@"block on myConcurrentDispatchQueue"); });该代码在 Concurrent Dispatch Queue 中运行指定的 Block。
另外,假设你部署的程序跑在 ios6 以上,那么 ARC 会自己主动管理,否则须要加入
dispatch_release(myConcurrentDispatchQueue);
Main Dispatch Queue/Global Dispatch Queue
另外一种方法是获取系统标准提供的 Dispatch Queue。
实际上不用特意生成 Dispatch Queue 系统也会给我们提供几个。
那就是 Main Dispatch Queue 和Global Dispatch Queue。
Main Dispatch Queue 正如其名称中含有的 Main 一样,是在主线程中运行的 Dispatch Queue。由于主线程仅仅有1个。所以 Main Dispatch Queue 自然就是 Serial Dispatch Queue。
追加到 Main Dispatch Queue 的处理在主线程的 RunLoop 中运行,因为在主线程中运行。因此要将用户界面的界面更新等一些必须在主线程中运行的处理追加到 Main Dispatch Queue 中使用。
还有一个 Global Dispatch Queue 是全部应用程序都可以使用的 Concurrent Dispatch Queue。没有必要通过 dispatch_queue_create 函数逐个生成 Concurrent Dispatch Queue。
仅仅要获取 Global Dispatch Queue 使用就可以。
另外,Global Dispatch Queue 有4个运行优先级,各自是高优先级,默认优先级,低优先级和后台优先级。
可是通过 XNU 内核用于 Global Dispatch Queue 的线程并不能保证实时性,因此运行优先级仅仅是大致的推断。
比如在处理内容的运行可有可无时,使用后台优先级的 Global Dispatch Queue 等,仅仅能进行这样的程度的区分。
// Main Dispatch Queue 的获取方法 dispatch_queue_t mainDispatchQueue=dispatch_get_main_queue(); // Global Dispatch Queue (高优先级)的获取方法 dispatch_queue_t globalDispatchQueueHigh= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); // Global Dispatch Queue (默认优先级)的获取方法 dispatch_queue_t globalDispatchQueueDefault= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // Global Dispatch Queue (低优先级)的获取方法 dispatch_queue_t globalDispatchQueueLow= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); // Global Dispatch Queue (后台优先级)的获取方法 dispatch_queue_t globalDispatchQueueBackground= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);下面举例了使用 Main Dispatch Queue 和Global Dispatch Queue 的代码。
// 在默认优先级的 Global Dispatch Queue 中运行 Block dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 并行运行的处理 // 在 Main Dispatch Queue 中运行 Block dispatch_async(dispatch_get_main_queue(), ^{ // 仅仅能在主线程中运行的处理 }); });
dispatch_set_target_queue
dispatch_set_target_queue 函数生成的 Dispatch Queue 无论是 Serial Dispatch Queue 还是 Concurrent Dispatch Queue。都使用与默认优先级 Global Dispatch Queue 同样运行优先级的线程。而变更生成的Dispatch Queue 的运行优先级要使用 dispatch_set_target_queue 函数。在后台运行动作处理的 Serial Dispatch Queue 的生成方法例如以下。
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.mySerialDispatchQueue", NULL); dispatch_queue_t globalDispatchQueueBackground= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0); dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackground);指定要变更运行优先级的 Dispatch Queue 为 dispatch_set_target_queue 函数的第一个參数。指定与要使用的运行优先级同样优先级的 Global Dispatch Queue 为第二个參数。
第一个參数不能指定系统提供的 Main Dispatch Queue 和 Global Dispatch Queue。
将 Dispatch Queue 指定为 dispatch_set_target_queue 函数的參数,不仅能够变更 Dispatch Queue 的运行优先级。还能够作成 Dispatch Queue 的运行阶层。假设在多个 Serial Dispatch Queue 中用 dispatch_set_target_queue 函数指定目标为某一个 Serial Dispatch Queue,那么原先本应并行运行的多个 Serial Dispatch Queue,在目标 Serial Dispatch Queue 上仅仅能同一时候运行一个处理。
在必须将不可并行运行的处理追加到多个 Serial Dispatch Queue 中时。假设使用 dispatch_set_target_queue 函数将目标指定为某一个 Serial Dispatch Queue,就可以防止处理并行运行。
dispatch_after
常常会有这种情况:想在3秒后运行处理。这种想在指定时间后运行处理的情况,可使用 dispatch_after 函数来实现。
在3秒后将指定的 Block 追加到 Main Dispatch Queue 中的源码例如以下:
dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC); dispatch_after(time, dispatch_get_main_queue(), ^{ NSLog(@"waited at least three seconds."); });要注意是,dispatch_after 函数并非在指定时间后运行处理,而仅仅是在指定时间追加处理到 Dispatch Queue。
此源码与在3秒后用 dispatch_async 函数追加 Block 到 Main Dispatch Queue 的同样。
由于 Main Dispatch Queue 在主线程的 RunLoop 中运行。比方每隔1/60秒运行的 RunLoop 中,Block 最快在3秒后运行。最慢在3秒+1/60秒后运行,而且在 Main Dispatch Queue 有大量处理追加或主线程的处理本身有延时时。这个时间会更长。
尽管在有严格时间的要求下使用时会出现故障,但在想大致延迟运行处理时,该函数是很有效的。
第二个參数指定要追加处理的 Dispatch Queue,第三个參数指定记述要运行处理的 Block。
第一个參数是指定时间用的 dispatch_time_t 类型的值。
该值使用 dispatch_time 函数或者 dispatch_walltime 函数作成。
dispatch_time 函数可以获取从第一个參数 dispatch_time_t 类型值中指定的时间開始,到第二个參数指定的毫微秒单位时间后的时间。
第一个參数常常使用的值是之前源码中出现的 DISOATCH_TIME_NOW。表示如今的时间。
数值和 NSEC_PER_SEC 的乘积得到单位为毫微秒的数值。
ull 是C语言的数值字面量,表示 unsigned long long。
假设使用 NSEC_PER_MSEC 则能够以毫秒为单位计算。
Dispatch Group
在追加到 Dispatch Queue 中的多个处理所有结束后想运行结束处理,这样的情况会常常出现。仅仅使用一个 Serial Dispatch Queue 时,仅仅要将想运行的处理所有追加到该 Serial Dispatch Queue 中并在最后追加结束处理。就可以实现。可是在使用 Concurrent Dispatch Queue 时或同一时候使用多个 Dispatch Queue 时。源码会变得颇为复杂。
在此情况下使用 Dispatch Group。比如以下代码为:追加3个 Block 到 Global Dispatch Queue。这些 Block 假设所有运行完成。就会运行 Main Dispatch Queue 中结束处理用的 Block。
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group=dispatch_group_create(); dispatch_group_async(group, queue, ^{ NSLog(@"blk0"); }); dispatch_group_async(group, queue, ^{ NSLog(@"blk1"); }); dispatch_group_async(group, queue, ^{ NSLog(@"blk2"); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"done"); });运行结果例如以下:
2014-10-06 14:49:29.929 GCD[2197:111113] blk2 2014-10-06 14:49:29.929 GCD[2197:111112] blk1 2014-10-06 14:49:29.929 GCD[2197:111111] blk0 2014-10-06 14:49:29.941 GCD[2197:111019] done由于向 Global Dispatch Queue 即 Concurrent Dispatch Queue 追加处理,多个线程并行运行,所以追加处理的运行顺序不定。
运行时会发生变化。可是此运行结果的 done 一定是最后输出的。
不管向什么样的 Dispatch Queue 中追加处理,使用 Dispatch Group 都可监视这些处理运行的结束。一旦检測到全部处理运行结束,就可将结束的处理追加到 Dispatch Queue 中。
这就是使用 Dispatch Group 的原因。
首先 dispatch_group_create 函数生成 dispatch_group_t 类型的 Dispatch Group。 如 dispatch_group_create 函数名所含的 create 所看到的。
dipatch_group_async 函数与 dispatch_async 函数同样,都追加 Block 到指定的 Dispatch Queue 中。与 dispatch_async 函数不同的是指定生成的 Dispatch Group 为第一个參数。指定的 Block 属于指定的 Dispatch Group。
在追加到 Dispatch Group 中的处理所有运行结束时,该源代码中使用的 dispatch_group_notify 函数会将运行的 Block 追加到 Dispatch Queue 中,将第一个參数指定为想要监视的 Dispatch Group。在追加到该 Dispatch Group 的所有处理运行结束时,将第三个參数的 Block 追加到第二个參数的 Dispatch Queue 中。在 dispatch_group_notify 函数中无论指定什么样的 Dispatch Queue。属于 Dispatch Group 的所有处理在追加指定的 Block 时都已运行结束。
另外,在 Dispatch Group 中也能够使用 dispatch_group_wait 函数仅等待所有处理运行结束。
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_t group=dispatch_group_create(); dispatch_group_async(group, queue, ^{ NSLog(@"blk0"); }); dispatch_group_async(group, queue, ^{ NSLog(@"blk1"); }); dispatch_group_async(group, queue, ^{ NSLog(@"blk2"); }); dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_group_wait 函数的第二个參数指定为等待的时间(超时),它属于 dispatch_time_t 类型的值。DISPATCH_TIME_FOREVER,意味着永久等待。
仅仅要属于 Dispatch Group 的处理尚未运行结束,就会一直等待。中途不能取消。
如同 dispatch_after 函数说明中出现的那样,指定等待间隔为1秒时应该做例如以下处理。
dispatch_time_t time=dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC); long result=dispatch_group_wait(group, time); if (result==0) { // 所有处理结束 }else{ // 某一个处理还在运行 }
假设 dispatch_group_wait 函数的返回值不为0。就意味着尽管经过了指定时间。但属于 Dispatch Group 的某一个处理还在运行中。假设返回值为0,那么所有处理运行结束。当等待时间为 DISPATCH_TIME_FOREVER,由 dispatch_group_wait 函数返回时,因为属于 Dispatch Group 的处理必然所有运行结束。因此返回值恒为0。
指定 DISPATCH_TIME_NOW,则不用不论什么等待即能够推断属于 Dispatch Group 的处理是否运行结束。
long result=dispatch_group_wait(group,DISPATCH_TIME_NOW)在主线程的 RunLoop 的每次循环中,可检查运行是否结束,从而不消耗多余的等待时间。尽管这样也能够,还是推荐使用 dispatch_group_notify 函数追加结束处理到 Main Dispatch Queue 中。这是由于 dispatch_group_notify 函数能够简化源码。
dispatch_barrier_async
在訪问数据库或者文件时,如前所述,使用 Serial Dispatch Queue 能够避免数据竞争的问题。
写入处理确实不可与其它的写入处理以及包括读取处理的其它某些处理并行运行。可是假设读取处理仅仅是与读取处理并行运行。那么多个并行运行就不会发生故障。
也就是说,为了高效率地进行訪问。读取处理追加到 Concurrent Dispatch Queue 中,写入处理在任一个读取处理没有运行的状态下,追加到 Serial Dispatch Queue 中就可以。
尽管利用 Dispatch Group 和 dispatch_set_target_queue 函数也可实现,可是源码会非常复杂。
GCD 为我们提供了更为聪明的解决的方法-dispatch_barrier_async 函数。该函数和 dispatch_queue_create 函数生成的 Concurrent Dispatch Queue 一起使用。
首先 dispatch_queue_create 函数生成 Concurrent Dispatch Queue。在 dispatch_async 中追加读取处理。
dispatch_queue_t queue=dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, blk0_for_reading); dispatch_async(queue, blk1_for_reading); dispatch_async(queue, blk2_for_reading); dispatch_async(queue, blk3_for_reading);在 blk1 和 blk2 之间运行写入处理,并将写入的内容读取 blk2 处理以及之后的处理中。
dispatch_queue_t queue=dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, blk0_for_reading); dispatch_async(queue, blk1_for_reading); dispatch_barrier_async(queue, blk_for_writing); dispatch_async(queue, blk2_for_reading); dispatch_async(queue, blk3_for_reading);如上所看到的。用法很easy,使用 dispatch_barrier_async 函数取代 dispatch_async 函数就可以。
dispatch_sync
dispatch_async 函数的 async 意味着 非同步,就是将指定的 Block 非同步 地追加到指定的 Dispatch Queue 中。
dispatch_async 函数不做不论什么等待。
既然有 async,当然有 sync。在追加 Block 结束之前,dispatch_sync 函数会一直等待。
我们先如果这样一种情况:运行 Main Dispatch Queue 时。使用另外的线程 Global Dispatch Queue 进行处理。处理结束后马上使用所得到的结果。在这样的情况下使用 dispatch_sync 函数。
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_sync(queue, ^{ NSLog(@"处理"); });另外,由 dispatch_barrier_async 函数中含有 async 可猜測出,对应地也有 dispatch_barrier_sync 函数。dispatch_barrier_async 函数的作用是等待追加的处理所有运行结束后,再追加处理到 Dispatch Queue 中,此外。它还与 dispatch_sync 函数同样,会等待追加处理的运行结束。
dispatch_apply
dispatch_apply 函数是 dispatch_sync 函数和 Dispatch Group 的关联 API。该函数按指定的次数将指定的 Block 追加到指定的 Dispatch Queue 中。并等待所有处理运行结束。
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_apply(10, queue, ^(size_t index){ NSLog(@"%zu",index); }); NSLog(@"done");运行结果为
2014-10-07 10:18:18.510 GCD[612:60b] 0 2014-10-07 10:18:18.510 GCD[612:3503] 2 2014-10-07 10:18:18.513 GCD[612:60b] 4 2014-10-07 10:18:18.510 GCD[612:3403] 3 2014-10-07 10:18:18.513 GCD[612:60b] 6 2014-10-07 10:18:18.514 GCD[612:60b] 8 2014-10-07 10:18:18.514 GCD[612:60b] 9 2014-10-07 10:18:18.513 GCD[612:3503] 5 2014-10-07 10:18:18.510 GCD[612:1303] 1 2014-10-07 10:18:18.513 GCD[612:3403] 7 2014-10-07 10:18:18.515 GCD[612:60b] done由于在 Global Dispatch Queue 中运行处理。所以各个处理的运行时间不定。
可是输出结果中最后的 done 必然在最后的位置上。这是由于 dispatch_apply 函数会等待所有处理运行结束。
第一个參数为反复次数,第二个參数为追加对象的 Dispatch Queue,第三个參数为追加的处理。与到眼下为止所出现的样例不同。第三个參数的 Block 为带有參数的 Block。这是为了按第一个參数反复追加 Block 并区分各个 Block 而使用。
dispatch_suspend/dispatch_resume
当追加大量处理到 Dispatch Queue 时,在追加处理的过程中,有时希望不运行已追加的处理。
在这样的情况下,仅仅要挂起 Dispatch Queue 就可以。
当能够运行时再恢复。
挂起的函数
dispatch_suspend(queue);恢复的函数
dispatch_resume(queue);这些函数对已经运行的处理没有影响。挂起后。追加到 Dispatch Queue 中但尚未运行的处理在此之后停止运行。
而恢复则使得这些处理可以继续运行。
dispatch_once
dispatch_once 函数是保证在应用程序运行中仅仅运行一次指定处理的 API。以下这样的常常出现的用来进行初始化的源码可通过 dispatch_once 函数简化。
static int initialized = NO; if (initialized==NO) { //初始化 initialized=YES; }假设使用 dispatch_once 函数。则源码为
static dispatch_once_t pred; dispatch_once (&pred,^{ //初始化 });源码看起来没有太大变化,可是通过 dispatch_once 函数。该源码即使在多线程环境下运行,也可保证百分之百安全。
之前的代码在大多数情况下也是安全的,可是在多核 CPU 中,在正在更新表示是否初始化的标志变量时读取,就有可能多次运行初始化处理。
GCD 实现
Dispatch Source
GCD 中除了基本的 Dispatch Queue 外,还有不太引人注目的 Disaptch Source。它是 BSD 系内核惯有功能 kqueue 的包装。
kqueue 是在 XNU 内核中发生各种事件时,在应用程序编程方运行处理的技术。其 CPU 负荷很小,尽量不占用资源。
使用 DISPATCH_SOURCE_TYPE_TIMER 的定时器的样例。在网络编程的通信超时等情况下能够使用该样例。
// 指定 DISPATCH_SOURCE_TYPE_TIMER, 作为 Dispatch Source // 在定时器经过指定时间时设定 Main Dispatch Queue 为追加处理的 Dispatch Queue dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); // 将定时器设为2秒后 // 不指定为反复 // 同意延时1秒 dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 1ull * NSEC_PER_SEC); // 指定定时器指定时间内运行的处理 dispatch_source_set_event_handler(timer, ^{ NSLog(@"wakeup!"); dispatch_source_cancel(timer); }); // 指定取消 Dispatch Source 时的处理 dispatch_source_set_cancel_handler(timer, ^{ NSLog(@"canceled"); //dispatch_release(timer); }); // 启动Dispatch Source dispatch_resume(timer);实际上 Dispatch Queue 没有“取消”这一概念。
一旦将处理追加到 Dispatch Queue 中,就没有方法能够将该处理去除,也没有方法能够在运行中取消该处理。假设要取消,考虑 NSOperationQueue 等其它方法。
Dispatch Source 与 Dispatch Queue 不同,是能够取消的。并且取消时必须运行的处理可指定为回调的Block形式。
因此使用 Dispatch Source 实现 XNU 内核中发生的事件处理要比直接使用 kqueue 实现更为简单。
example
http://download.csdn.net/detail/gwh111/8008367