dispatch ios_ios-dispatch - CSDN
  • 谈起iOSdispatch(正式称谓是Grand Central Dispatch或GCD),不得不说这又是iOS(包括MacOSX)平台的创新,优缺点这里不讨论,只有当你使用时才能真正体会到。我们说dispatch函数的主要目的是实现多任务并发代码...

    原文地址  http://www.cnblogs.com/sunfrog/p/3243230.html


    谈起iOS的dispatch(正式称谓是Grand Central Dispatch或GCD),不得不说这又是iOS(包括MacOSX)平台的创新,优缺点这里不讨论,只有当你使用时才能真正体会到。我们说dispatch函数的主要目的是实现多任务并发代码,那么要理解dispatch函数,先来了解dispatch对象的定义。

     

    dispatch对象类型的部分定义,主要使用C语言的宏定义:

    <os/object.h>文件:

    #define OS_OBJECT_CLASS(name) OS_##name

    #define OS_OBJECT_DECL(name, ...) \

    @protocol OS_OBJECT_CLASS(name) __VA_ARGS__ \

    @end \

    typedef NSObject<OS_OBJECT_CLASS(name)> *name##_t

    #define OS_OBJECT_DECL_SUBCLASS(name, super) \

    OS_OBJECT_DECL(name, <OS_OBJECT_CLASS(super)>)

     

    <dispatch/object.h>文件:

    #define DISPATCH_DECL(name) OS_OBJECT_DECL_SUBCLASS(name, dispatch_object)

    #define DISPATCH_GLOBAL_OBJECT(type, object) ((OS_OBJECT_BRIDGE type)&(object))

    OS_OBJECT_DECL(dispatch_object); //定义dispatch_object_t

     

    <dispatch/queue.h>文件(dispatch队列类定义,其它dispatch对象类似):

    DISPATCH_DECL(dispatch_queue); //定义dispatch_queue_t

     

    可以通过Xcode预编译后可以看到最终结果,最终定义的都是NSObject类,虽然它们之间没用直接继承关系,但都实现OS_dispatch_object接口,这样dispatch_queue_t对象也同样是dispatch_object_t的对象了。下面就是预编译dispatch_object_t和dispatch_queue_t的结果:

    @protocol OS_dispatch_object

    @end

    typedef NSObject<OS_dispatch_object> *dispatch_object_t;

    @protocol OS_dispatch_queue <OS_dispatch_object>

    @end

    typedef NSObject<OS_dispatch_queue> *dispatch_queue_t;

     

    由于dispatch api接口定义成C函数的形式,dispatch的对象都是由C函数形式的厂方法得到(不能继承dispatch类,不用alloc),这样做隐藏dispatch对象的具体形态,把注意力放在如何调用dispatch api上。

    从上面dispatch对象宏定义可以看到dispatch对象类的名称一般为dispatch_xyz_t(严格来讲是对象指针),它们都可以看成dispatch_object_t的子类(对象指针),所以使用dispatch对象时套用这个概念就行。

     

    有关dispatch对象的基本接口如下:

    void dispatch_retain(dispatch_object_t object); //替代dispatch对象常规的retain来持有对象,但ARC编程中不再允许

    void dispatch_release(dispatch_object_t object); //替代dispatch对象常规的release来释放对象,同样ARC编程中不再允许

    void dispatch_set_context(dispatch_object_t object, void *context); //给dispatch对象绑定特定数据对象(类似线程的TLS数据),会被传给dispatch对象的finalizer函数

    void *dispatch_get_context(dispatch_object_t object); //返回dispatch对象绑定的数据对象指针

    void dispatch_set_finalizer_f(dispatch_object_t object, dispatch_function_t finalizer); //设置dispatch对象的finalizer函数,当该对象释放时会调用finalizer,部分代码解释如何使用这个函数(ARC模式):

    dispatch_object_t dispatchObject = ...;

    void *context = ...;

    dispatch_set_context(dispatchObject, context);

    dispatch_set_finalizer_f(dispatchObject, finalizer);

    ......

    dispatchObject = nil; //dispatchObject被释放,这时调用finalizer函数

    ......

    void finalizer(void *context)

    {

      //处理或释放context相关资源

    }

     

    dispatch对象的另外两个接口是:

    void dispatch_resume(dispatch_object_t object); //激活(启动)在dispatch对象上的block调用,可以运行多个block

    void dispatch_suspend(dispatch_object_t object); //挂起(暂停)在dispatch对象上的block调用,已经运行的block不会停止

    一般这两个函数的调用必须成对,否则运行会出现异常。

    至此你是否发现这两个函数有些与众不同呢?好像从来没有这么使用对象的,启动对象--暂停对象,呵呵。这正是理解dispatch对象的关键所在。dispatch对象其实是抽象的任务,把动态的任务变成对象来管理。任务是动态的,不存在继承关系,这就是为什么GCD没有提供静态继承dispatch对象类的方式。如果能这样理解,那么在使用dispatch函数时就能够更灵活地去编写代码,实现各种并发的多任务代码。

     

     

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

    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

    展开全文
  • GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行,它类似NSOperationQueue但更复杂也更强大,并且可以嵌套使用。所以说,结合block实现的GCD,把函数闭包(Closure)的特性发挥得...


    GCD编程的核心就是dispatch队列,dispatch block的执行最终都会放进某个队列中去进行,它类似NSOperationQueue但更复杂也更强大,并且可以嵌套使用。所以说,结合block实现的GCD,把函数闭包(Closure)的特性发挥得淋漓尽致。

     

    dispatch队列的生成可以有这几种方式:

    1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial"DISPATCH_QUEUE_SERIAL); //生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。

    2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent"DISPATCH_QUEUE_CONCURRENT); //生成一个并发执行队列,block被分发到多个线程去执行

    3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。需要注意的是,三个队列不代表三个线程,可能会有更多的线程。并发队列可以根据实际情况来自动产生合理的线程数,也可理解为dispatch队列实现了一个线程池的管理,对于程序逻辑是透明的。

    官网文档解释说共有三个并发队列,但实际还有一个更低优先级的队列,设置优先级为DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode调试时可以观察到正在使用的各个dispatch队列。

    4. dispatch_queue_t queue = dispatch_get_main_queue(); //获得主线程的dispatch队列,实际是一个串行队列。同样无法控制主线程dispatch队列的执行继续或中断。

    接下来我们可以使用dispatch_async或dispatch_sync函数来加载需要运行的block。

    dispatch_async(queue, ^{

      //block具体代码

    }); //异步执行block,函数立即返回

    dispatch_sync(queue, ^{

      //block具体代码

    }); //同步执行block,函数不返回,一直等到block执行完毕。编译器会根据实际情况优化代码,所以有时候你会发现block其实还在当前线程上执行,并没用产生新线程。

    实际编程经验告诉我们,尽可能避免使用dispatch_sync,嵌套使用时还容易引起程序死锁。

    如果queue1是一个串行队列的话,这段代码立即产生死锁:

       dispatch_sync(queue1, ^{

          dispatch_sync(queue1, ^{

        ......

      });

      ......

     });

    不妨思考下,为什么下面代码也肯定死锁:

    dispatch_sync(dispatch_get_main_queue(), ^{

      ......

    }); 

     

    那实际运用中,一般可以用dispatch这样来写,常见的网络请求数据多线程执行模型:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

      //子线程中开始网络请求数据

      //更新数据模型

      dispatch_sync(dispatch_get_main_queue(), ^{

        //在主线程中更新UI代码

      });

    });

    程序的后台运行和UI更新代码紧凑,代码逻辑一目了然。

     

    dispatch队列是线程安全的,可以利用串行队列实现锁的功能。比如多线程写同一数据库,需要保持写入的顺序和每次写入的完整性,简单地利用串行队列即可实现:

    dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb"DISPATCH_QUEUE_SERIAL);

    - (void)writeDB:(NSData *)data

    {

      dispatch_async(queue1, ^{

        //write database

      });

    } 

    下一次调用writeDB:必须等到上次调用完成后才能进行,保证writeDB:方法是线程安全的。 

     

    dispatch队列还实现其它一些常用函数,包括:

    void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。

    void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。

    void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函数

    void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延迟执行block

    最后再来看看dispatch队列的一个很有特色的函数:

    void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

    它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源(以后博文会介绍)。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:

    dispatch_set_target_queue(dispatchA, dispatchB);

    那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:

    dispatch_suspend(dispatchA);

    则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。

    这里只简单举个例子,说明dispatch队列运行的灵活性,在实际应用中你会逐步发掘出它的潜力。

    dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的缺憾。 


    展开全文
  • dispatch_queue help both the system and your application to run faster, more efficiently, and with improved responsiveness. Grand Central Dispatch (GCD) comprises language features, runtime libra...

    GCD系列是阅读官方文件和在实践中总结的一些常见的GCD用法,基本涉及全部的GCD内容形成的集合文章,文章重点在与精简和全面覆盖。

    学习本集合你可以了解:
    1. GCD是如何做到多线程调度的
    2. 对比其他iOS的多线程方案,GCD的优势和劣势
    3. 如何使用GCD相关的API

    我将在后续不断补充详细内容和实际案例, 欢迎关注,提问和讨论

    01-dispatch-iOS系统调度

    02-dispatch_queue-调度队列

    03-dispatch_after/dispatch_time-延迟调度和操作

    04-dispatch_barrier_sync/async-线程阻塞

    05-dispatch_apply-重复提交操作

    06-dispatch_once-单次执行

    07-dispatch_semaphore-信号量/数据同步

    08-dispatch_group-调度组/多异步操作控制

    09-dispatch_block-GCD取消操作

    10-dispatch_source-调度资源

    11-GCD死锁

    dispatch_queue

    help both the system and your application to run faster, more efficiently, and with improved responsiveness.
        Grand Central Dispatch (GCD) comprises language features, runtime libraries, and system enhancements that provide systemic, comprehensive improvements to the support for concurrent code execution on multicore hardware
        GCD, operating at the system level, can better accommodate the needs of all running applications, matching them to the available system resources in a balanced fashion.
        GCD provides and manages FIFO queues to which your application can submit tasks in the form of block objects. Work submitted to dispatch queues are executed on a pool of threads fully managed by the system. No guarantee is made as to the thread on which a task executes.

    dispatch是一个简单强大的表达并发的抽象模型

    • 在dispatch的内部,dispatch提供一系列提交的block先进先出的队列。
    • block被提交到一系列由系统管理的线程池中,系统不保证这个Block会在哪一个线程执行。
    • 多个队列提交Block的时候,系统会自动创建新的线程来并发执行这些Block,当线程执行完毕时,会被系统自动释放。

    • dispatch会按照先进先出的顺序每次调用一个Block去执行

    • 不同的队列Queue之间不受影响,也就是说不同队列之间是并发执行Block

    • dispatch queue(调度队列)相对于提交的Block块来说事轻量级的

    • 系统管理了一个线程池来处理调度队列和执行提交到队列的Block
    • 调度队列有自己的执行线程,并且队列之间的交互是高度异步的

    • 调度队列是通过dispatch_retain()和dispatch_release()来执行引用计数的

    • 提交的Blocks会对Queue做一次引用直到执行完毕,当Queue的所有引用都被释放的时候,队列就会被系统销毁

    dispatch_queue的主要API

    void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
    
    void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
    void dispatch_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
    
    dispatch_queue_t dispatch_get_main_queue(void) {
        return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
    }
    
    #define DISPATCH_QUEUE_PRIORITY_HIGH 2
    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
    #define DISPATCH_QUEUE_PRIORITY_LOW (-2)
    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
    typedef long dispatch_queue_priority_t;
    
    dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
    
    void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
    void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
    
    void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
    void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
    
    void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
    void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
    
    void dispatch_apply(size_t iterations, dispatch_queue_t queue, DISPATCH_NOESCAPE void (^block)(size_t));
    void dispatch_apply_f(size_t iterations, dispatch_queue_t queue, void *_Nullable context, void (*work)(void *_Nullable, size_t));
    展开全文
  • iOS Dispatch

    2018-12-14 11:46:03
    简述 队列有串行队列和并发队列,线程有同步一个异步,我们今天看一下两个组合的结果 并行+同步
  • [iOS]GCD Dispatch系列

    2018-09-20 10:15:42
    dispatch_sync 同步执行,直到执行的任务完成才返回 目标队列:串行队列 在当前线程执行任务,顺序执行 目标队列:并发队列 新建线程执行任务,顺序执行 目标队列:主线程队列 在主线程执行任务,顺序执行 在当前线程...
  • 同样在,在ios移动开发和Android基本是很类似的一种模型。 但是很多时候,在应用开发中,我们会发现本身并没有自己编码去处理一些并发的事件,去开辟新的子线程等等。 (虽然一般的调用sdk发起一个网络请求,系统都...
  • 1. dispatch_apply2. dispatch_barrier_async
  • 一,兴趣是最好的老师。 在IOS开发中,为保证单例在整个程序运行中只被初始化一次,单线程的时候,通过静态变量可以实现;...苹果提供了dispatch_once(dispatch_once_t *predicate,dispatch_block_t ...
  • IOS dispatch_once

    2013-01-14 20:56:50
    dispatch_once Executes a block object once and only once for the lifetime of an application. void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block); Parameters predica
  • 导读: 本文为读《Concurrency Programming Guide》笔记第三篇,在对OS X和iOS应用开发中实现任务异步执行的技术、注意事项、Operation与Dispatch Queues实践解析后,作者付宇轩(@DevTalking)着重分享了让Dispatch...
  • 介绍:Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 ,iOS ...
  • iOS dispatch_group_t

    2016-06-28 17:37:35
    group异步执行dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, block);group执行完毕通知dispatch_group_notify(dispathc_group_t group, dispatch_queue_t queue, bloc
  • 一、dispatch_after 二、dispatch_apply  三、dispatch_once 一、dispatch_after 功能:延迟一段时间把一项任务提交到队列中执行,返回之后就不能取消 常用来在在主队列上延迟执行一项任务 函数原型 func ...
  • iOS dispatch_source_t

    2017-12-04 17:04:10
    Dispatch Source是GCD中的一个基本类型,从字面意思可称为调度源,它的作用是当有一些特定的较底层的系统事件发生时,调度源会捕捉到这些事件,然后可以做其他的逻辑处理,调度源有多种类型,分别监听对应类型的系统...
  • iOS dispatch_group的使用

    2016-12-02 16:18:23
    * 使用dispatch_group,异步多请求 */ - (void)asyncBaseData { // 全局串行队列 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); // 创建一个group dispatch_group_t group = dispatch_...
  • iOS dispatch_time_t

    2016-06-30 09:50:34
    创建dispatch_time_tdispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);#define NSEC_PER_SEC 1000000000ull #define NSEC_PER_MSEC 1000000ull #define USEC_PER_SEC 1000000ull #define NSEC_...
  • iOS 使用 dispatch_once 创建线程安全的单例
  • dispatch_group_t group = dispatch_group_create(); // 执行第一个下载任务 dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ // 下载完毕... }); // 执行第二个下载任务 dispatch_group_...
  • 该篇是 「iOS 多线程」系列的第三篇.前面两篇可以戳击下面的链接.iOS 多线程: 正确认识 GCD 队列类型 iOS多线程: 信号量该系列博客是为了记录和分享自己关于 iOS 多线程的一些认识, 结合工作中遇到的问题, 和大家...
1 2 3 4 5 ... 20
收藏数 38,111
精华内容 15,244
关键字:

dispatch ios