iOS并发编程指南--Dispatch Sources

来源:互联网 发布:炫踪网络出的游戏 编辑:程序博客网 时间:2024/04/28 18:00

Dispatch Sources

现代系统通常提供异步接口,允许应用向系统提交请求,然后在系统处理请求时应用可以继续处理自己的事情。Grand Central Dispatch正是基于这个基本行为而设计,允许你提交请求,并通过block和dispatch queue报告结果。

dispatch source是基础数据类型,协调特定底层系统事件的处理。Grand Central Dispatch支持以下dispatch source:

Timer dispatch source:定期产生通知

Signal dispatch source:UNIX信号到达时产生通知

Descriptor dispatch source:各种文件和socket操作的通知

数据可读

数据可写

文件在文件系统中被删除、移动、重命名

文件元数据信息改变

Process dispatch source:进程相关的事件通知

当进程退出时

当进程发起fork或exec等调用

信号被递送到进程

Mach port dispatch source:Mach相关事件的通知

Custom dispatch source:你自己定义并自己触发

Dispatch source替代了异步回调函数,来处理系统相关的事件。当你配置一个dispatch source时,你指定要监测的事件、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本身。事件源是处理事件所需要的native数据结构,例如基于描述符的dispatch source,你需要打开描述符;基于进程的事件,你需要获得目标程序的进程ID。

然后可以如下创建相应的dispatch source:

使用 dispatch_source_create 函数创建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。

函数事件处理器有一个context指针指向dispatch source对象,没有返回值。Block事件处理器没有参数,也没有返回值。

  1. // Block-based event handler  
  2. void (^dispatch_block_t)(void)  
  3.    
  4. // Function-based event handler  
  5. void (*dispatch_function_t)(void *)  

在事件处理器中,你可以从dispatch source中获得事件的信息,函数处理器可以直接使用参数指针,Block则必须自己捕获到dispatch source指针,一般block定义时会自动捕获到外部定义的所有变量。

  1. dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, 
  2.                                  myDescriptor, 0, myQueue); 
  3. dispatch_source_set_event_handler(source, ^{ 
  4.    // Get some data from the source variable, which is captured 
  5.    // from the parent context. 
  6.    size_t estimated = dispatch_source_get_data(source); 
  7.   
  8.    // Continue reading the descriptor... 
  9. }); 
  10. dispatch_resume(source); 

Block捕获外部变量允许更大的灵活性和动态性。当然,在Block中这些变量默认是只读的,虽然可以使用__block来修改捕获的变量,但是你最好不要在事件处理器中这样做。因为Dispatch source异步执行事件处理器,当事件处理器修改原始外部变量时,有可能这些变量已经不存在了。

下面是事件处理器能够获得的事件信息:

函数描述dispatch_source_get_handle这个函数返回dispatch source管理的底层系统数据类型。

对于描述符dispatch source,函数返回一个int,表示关联的描述符

对于信号dispatch source,函数返回一个int,表示最新事件的信号数值

对于进程dispatch source,函数返回一个pid_t数据结构,表示被监控的进程

对于Mach port dispatch source,函数返回一个 mach_port_t 数据结构

对于其它dispatch source,函数返回的值未定义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,函数返回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 函数来设置取消处理器。

下面取消处理器关闭描述符:

  1. dispatch_source_set_cancel_handler(mySource, ^{ 
  2.    close(fd); // Close a file descriptor opened earlier. 
  3. }); 

修改目标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应用单一全局事件时,可以让它自己处理该事件,并立即退出。

Dispatch Source示例

创建一个定时器

定时器dispatch source定时产生事件,可以用来发起定时执行的任务,如游戏或其它图形应用,可以使用定时器来更新屏幕或动画。你也可以设置定时器,并在固定间隔事件中检查服务器的新信息。

所有定时器dispatch source都是间隔定时器,一旦创建,会按你指定的间隔定期递送事件。你需要为定时器dispatch source指定一个期望的定时器事件精度,也就是leeway值,让系统能够灵活地管理电源并唤醒内核。例如系统可以使用leeway值来提前或延迟触发定时器,使其更好地与其它系统事件结合。创建自己的定时器时,你应该尽量指定一个leeway值。

就算你指定leeway值为0,也不要期望定时器能够按照精确的纳秒来触发事件。系统会尽可能地满足你的需求,但是无法保证完全精确的触发时间。

当计算机睡眠时,定时器dispatch source会被挂起,稍后系统唤醒时,定时器dispatch source也会自动唤醒。根据你提供的配置,暂停定时器可能会影响定时器下一次的触发。如果定时器dispatch source使用 dispatch_time 函数或 DISPATCH_TIME_NOW 常量设置,定时器dispatch source会使用系统默认时钟来确定何时触发,但是默认时钟在计算机睡眠时不会继续。

如果你使用 dispatch_walltime 函数来设置定时器dispatch source,则定时器会根据挂钟时间来跟踪,这种定时器比较适合触发间隔相对比较大的场合,可以防止定时器触发间隔出现太大的误差。

下面是定时器dispatch source的一个例子,每30秒触发一次,leeway值为1,因为间隔相对较大,使用 dispatch_walltime 来创建定时器。定时器会立即触发第一次,随后每30秒触发一次。 MyPeriodicTask 和 MyStoreTimer 是自定义函数,用于实现定时器的行为,并存储定时器到应用的数据结构。

  1. dispatch_source_t CreateDispatchTimer(uint64_t interval, 
  2.               uint64_t leeway, 
  3.               dispatch_queue_t queue, 
  4.               dispatch_block_t block) 
  5.    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 
  6.                                                      0, 0, queue); 
  7.    if (timer) 
  8.    { 
  9.       dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway); 
  10.       dispatch_source_set_event_handler(timer, block); 
  11.       dispatch_resume(timer); 
  12.    } 
  13.    return timer; 
  14.   
  15. void MyCreateTimer() 
  16.    dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC, 
  17.                                1ull * NSEC_PER_SEC, 
  18.                                dispatch_get_main_queue(), 
  19.                                ^{ MyPeriodicTask(); }); 
  20.   
  21.    // Store it somewhere for later use. 
  22.     if (aTimer) 
  23.     { 
  24.         MyStoreTimer(aTimer); 
  25.     } 

虽然定时器dispatch source是接收时间事件的主要方法,你还可以使用其它选择。如果想在指定时间间隔后执行一个block,可以使用 dispatch_after 或 dispatch_after_f 函数。这两个函数非常类似于dispatch_async,但是只允许你指定一个时间值,时间一到就自动提交block到queue中执行,时间值可以指定为相对或绝对时间。

从描述符中读取数据

要从文件或socket中读取数据,需要打开文件或socket,并创建一个 DISPATCH_SOURCE_TYPE_READ 类型的dispatch source。你指定的事件处理器必须能够读取和处理描述符中的内容。对于文件,需要读取文件数据,并为应用创建适当的数据结构;对于网络socket,需要处理最新接收到的网络数据。

读取数据时,你总是应该配置描述符使用非阻塞操作,虽然你可以使用 dispatch_source_get_data 函数查看当前有多少数据可读,但在你调用它和实际读取数据之间,可用的数据数量可能会发生变化。如果底层文件被截断,或发生网络错误,从描述符中读取会阻塞当前线程,停止在事件处理器中间并阻止dispatch queue去执行其它任务。对于串行queue,这样还可能会死锁,即使是并发queue,也会减少queue能够执行的任务数量。

下面例子配置dispatch source从文件中读取数据,事件处理器读取指定文件的全部内容到缓冲区,并调用一个自定义函数来处理这些数据。调用方可以使用返回的dispatch source在读取操作完成之后,来取消这个事件。为了确保dispatch queue不会阻塞,这里使用了fcntl函数,配置文件描述符执行非阻塞操作。dispatch source安装了取消处理器,确保最后关闭了文件描述符。

  1. dispatch_source_t ProcessContentsOfFile(const char* filename) 
  2.    // Prepare the file for reading. 
  3.    int fd = open(filename, O_RDONLY); 
  4.    if (fd == -1) 
  5.       return NULL; 
  6.    fcntl(fd, F_SETFL, O_NONBLOCK);  // Avoid blocking the read operation 
  7.   
  8.    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
  9.    dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, 
  10.                                    fd, 0, queue); 
  11.    if (!readSource) 
  12.    { 
  13.       close(fd); 
  14.       return NULL; 
  15.    } 
  16.   
  17.    // Install the event handler 
  18.    dispatch_source_set_event_handler(readSource, ^{ 
  19.       size_t estimated = dispatch_source_get_data(readSource) + 1; 
  20.       // Read the data into a text buffer. 
  21.       char* buffer = (char*)malloc(estimated); 
  22.       if (buffer) 
  23.       { 
  24.          ssize_t actual = read(fd, buffer, (estimated)); 
  25.          Boolean done = MyProcessFileData(buffer, actual);  // Process the data. 
  26.   
  27.          // Release the buffer when done. 
  28.          free(buffer); 
  29.   
  30.          // If there is no more data, cancel the source. 
  31.          if (done) 
  32.             dispatch_source_cancel(readSource); 
  33.       } 
  34.     }); 
  35.   
  36.    // Install the cancellation handler 
  37.    dispatch_source_set_cancel_handler(readSource, ^{close(fd);}); 
  38.   
  39.    // Start reading the file. 
  40.    dispatch_resume(readSource); 
  41.    return readSource; 

在这个例子中,自定义的 MyProcessFileData 函数确定读取到足够的数据,返回YES告诉dispatch source读取已经完成,可以取消任务。通常读取描述符的dispatch source在还有数据可读时,会重复调度事件处理器。如果socket连接关闭或到达文件末尾,dispatch source自动停止调度事件处理器。如果你自己确定不再需要dispatch source,也可以手动取消它。

向描述符写入数据

向文件或socket写入数据非常类似于读取数据,配置描述符为写入操作后,创建一个 DISPATCH_SOURCE_TYPE_WRITE 类型的dispatch source,创建好之后,系统会调用事件处理器,让它开始向文件或socket写入数据。当你完成写入后,使用 dispatch_source_cancel 函数取消dispatch source。

写入数据也应该配置文件描述符使用非阻塞操作,虽然 dispatch_source_get_data 函数可以查看当前有多少可用写入空间,但这个值只是建议性的,而且在你执行写入操作时可能会发生变化。如果发生错误,写入数据到阻塞描述符,也会使事件处理器停止在执行中途,并阻止dispatch queue执行其它任务。串行queue会产生死锁,并发queue则会减少能够执行的任务数量。

下面是使用dispatch source写入数据到文件的例子,创建文件后,函数传递文件描述符到事件处理器。MyGetData函数负责提供要写入的数据,在数据写入到文件之后,事件处理器取消dispatch source,阻止再次调用。此时dispatch source的拥有者需负责释放dispatch source。

  1. dispatch_source_t WriteDataToFile(const char* filename) 
  2.     int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 
  3.                       (S_IRUSR | S_IWUSR | S_ISUID | S_ISGID)); 
  4.     if (fd == -1) 
  5.         return NULL; 
  6.     fcntl(fd, F_SETFL); // Block during the write. 
  7.   
  8.     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
  9.     dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, 
  10.                             fd, 0, queue); 
  11.     if (!writeSource) 
  12.     { 
  13.         close(fd); 
  14.         return NULL; 
  15.     } 
  16.   
  17.     dispatch_source_set_event_handler(writeSource, ^{ 
  18.         size_t bufferSize = MyGetDataSize(); 
  19.         void* buffer = malloc(bufferSize); 
  20.   
  21.         size_t actual = MyGetData(buffer, bufferSize); 
  22.         write(fd, buffer, actual); 
  23.   
  24.         free(buffer); 
  25.   
  26.         // Cancel and release the dispatch source when done. 
  27.         dispatch_source_cancel(writeSource); 
  28.     }); 
  29.   
  30.     dispatch_source_set_cancel_handler(writeSource, ^{close(fd);}); 
  31.     dispatch_resume(writeSource); 
  32.     return (writeSource); 

监控文件系统对象

如果需要监控文件系统对象的变化,可以设置一个 DISPATCH_SOURCE_TYPE_VNODE 类型的dispatch source,你可以从这个dispatch source中接收文件删除、写入、重命名等通知。你还可以得到文件的特定元数据信息变化通知。

在dispatch source正在处理事件时,dispatch source中指定的文件描述符必须保持打开状态。

下面例子监控一个文件的文件名变化,并在文件名变化时执行一些操作(自定义的 MyUpdateFileName 函数)。由于文件描述符专门为dispatch source打开,dispatch source安装了取消处理器来关闭文件描述符。这个例子中的文件描述符关联到底层的文件系统对象,因此同一个dispatch source可以用来检测多次文件名变化。

  1. dispatch_source_t MonitorNameChangesToFile(const char* filename) 
  2.    int fd = open(filename, O_EVTONLY); 
  3.    if (fd == -1) 
  4.       return NULL; 
  5.   
  6.    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
  7.    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, 
  8.                 fd, DISPATCH_VNODE_RENAME, queue); 
  9.    if (source) 
  10.    { 
  11.       // Copy the filename for later use. 
  12.       int length = strlen(filename); 
  13.       char* newString = (char*)malloc(length + 1); 
  14.       newString = strcpy(newString, filename); 
  15.       dispatch_set_context(source, newString); 
  16.   
  17.       // Install the event handler to process the name change 
  18.       dispatch_source_set_event_handler(source, ^{ 
  19.             const char*  oldFilename = (char*)dispatch_get_context(source); 
  20.             MyUpdateFileName(oldFilename, fd); 
  21.       }); 
  22.   
  23.       // Install a cancellation handler to free the descriptor 
  24.       // and the stored string. 
  25.       dispatch_source_set_cancel_handler(source, ^{ 
  26.           char* fileStr = (char*)dispatch_get_context(source); 
  27.           free(fileStr); 
  28.           close(fd); 
  29.       }); 
  30.   
  31.       // Start processing events. 
  32.       dispatch_resume(source); 
  33.    } 
  34.    else 
  35.       close(fd); 
  36.   
  37.    return source; 

监测信号

应用可以接收许多不同类型的信号,如不可恢复的错误(非法指令)、或重要信息的通知(如子进程退出)。传统编程中,应用使用 sigaction 函数安装信号处理器函数,信号到达时同步处理信号。如果你只是想信号到达时得到通知,并不想实际地处理该信号,可以使用信号dispatch source来异步处理信号。

信号dispatch source不能替代 sigaction 函数提供的同步信号处理机制。同步信号处理器可以捕获一个信号,并阻止它中止应用。而信号dispatch source只允许你监测信号的到达。此外,你不能使用信号dispatch source获取所有类型的信号,如SIGILL, SIGBUS, SIGSEGV信号。

由于信号dispatch source在dispatch queue中异步执行,它没有同步信号处理器的一些限制。例如信号dispatch source的事件处理器可以调用任何函数。灵活性增大的代价是,信号到达和dispatch source事件处理器被调用的延迟可能会增大。

下面例子配置信号dispatch source来处理SIGHUP信号,事件处理器调用 MyProcessSIGHUP 函数,用来处理信号。

  1. void InstallSignalHandler() 
  2.    // Make sure the signal does not terminate the application. 
  3.    signal(SIGHUP, SIG_IGN); 
  4.   
  5.    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
  6.    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue); 
  7.   
  8.    if (source) 
  9.    { 
  10.       dispatch_source_set_event_handler(source, ^{ 
  11.          MyProcessSIGHUP(); 
  12.       }); 
  13.   
  14.       // Start processing signals 
  15.       dispatch_resume(source); 
  16.    } 

监控进程

进程dispatch source可以监控特定进程的行为,并适当地响应。父进程可以使用dispatch source来监控自己创建的所有子进程,例如监控子进程的死亡;类似地,子进程也可以使用dispatch source来监控父进程,例如在父进程退出时自己也退出。

下面例子安装了一个进程dispatch source,监控父进程的终止。当父进程退出时,dispatch source设置一些内部状态信息,告知子进程自己应该退出。MySetAppExitFlag 函数应该设置一个适当的标志,允许子进程终止。由于dispatch source自主运行,因此自己拥有自己,在程序关闭时会取消并释放自己。

  1. void MonitorParentProcess() 
  2.    pid_t parentPID = getppid(); 
  3.   
  4.    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
  5.    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, 
  6.                                                       parentPID, DISPATCH_PROC_EXIT, queue); 
  7.    if (source) 
  8.    { 
  9.       dispatch_source_set_event_handler(source, ^{ 
  10.          MySetAppExitFlag(); 
  11.          dispatch_source_cancel(source); 
  12.          dispatch_release(source); 
  13.       }); 
  14.       dispatch_resume(source); 
  15.    } 

取消一个Dispatch Source

除非你显式地调用 dispatch_source_cancel 函数,dispatch source将一直保持活动,取消一个dispatch source会停止递送新事件,并且不能撤销。因此你通常在取消dispatch source后立即释放它:

  1. void RemoveDispatchSource(dispatch_source_t mySource) 
  2.    dispatch_source_cancel(mySource); 
  3.    dispatch_release(mySource); 

取消一个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继续。但是不会递送所有事件,而是先合并到单一事件,然后再一次递送。例如你监控一个文件的文件名变化,就只会递送最后一次的变化事件。

感谢作者,支持原创,转至:http://blog.csdn.net/simbi/article/details/7410603

2 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 白色纯棉衣服染色了怎么办 红色硅胶壳黑了怎么办 小米6gps信号弱怎么办 网线头卡子断了怎么办 入户网线太短了怎么办 孩子弹钢琴大拇指出琴键怎么办 手指肿胀疼痛变粗怎么办 iphone系统占用内存大怎么办 手机系统占用内存大怎么办 头盔固定配件掉了怎么办 移动4g网络不好怎么办 wifi登录密码忘记了怎么办 电脑登录密码忘记了怎么办 笔记本电脑登录密码忘记了怎么办 信用卡登录密码忘记了怎么办 华为p9后置摄像头模糊怎么办 手机图片文件夹删了怎么办 发票系统导出的xml怎么办 微信支付被限制怎么办 跨行三天不到账怎么办 测速正常但实际很慢怎么办 善领wifi上网慢怎么办 文件太大拷贝不到u盘怎么办 电脑的视频文件不显示缩略图怎么办 无线网连接不上怎么办 路由器wifi密码忘了怎么办 路由器账号密码忘了怎么办 刷卡刷多了钱怎么办 刷卡刷了两次钱怎么办 小米手机强刷后一联网就被锁怎么办 苹果刷机出现的问题怎么办 小米手机解不开图案锁怎么办 小米6无限重启怎么办 小米5一直显示mi怎么办 小米手机电池进入休眠状态怎么办 小米3s开不开机怎么办 小米n充电关机开机不了怎么办? 关机后强制刷机怎么办 红米手机开机画面怎么办 红米note1无法清理数据怎么办 红米2a密码忘记怎么办