GCD 开发详情

目录

  一、简介

  二、dispatch Queue - 队列

  三、dispatch Groups - 组

  四、dispatch Semaphores - 信号量

  五、dispatch Barriers - 障碍

  六、dispatch sources - 系统源

  七、dispatch I/O - I/O

  八、总结

一、简介

  GCD 的全称是 Grand Centre Dispatch 是一个强大的任务编程管理工具。通过GCD你可以同步或者异步地执行block、function。

二、dispatch Queue - 队列

  queue是指先进先出的列表,是blocks的执行环境。这个环境可以是单线程的也可以使多线程这个取决于你创建的queue类型。如果是serial queue(串行队列)队列的话,queue内部是以单线程运行。被添加进去的blocks,按照它们被添加进去的顺序串行地执行。

  如果是concurrent queue(并行队列),queue的内部线程个数由被添加的block执行的任务和当前系统有关。一般来说线程是会共用的,即是当一个block用完一个线程后,这个线程不会立马销毁会留着备用。这样就减少了创建线程的系统消耗。

  我们下面看看怎么用queue相关的API

  1. 创建queue

    有两个创建queue的方法

    一个是通过dispatch_queue_create手动创建queue,这个方法可以创建串行queue、并行queue

  函数定义:  

dispatch_queue_t dispatch_queue_create( const char *label dispatch_queue_attr_t attr);
参数 label 是指这个queue名字
参数 attr 可以指出需要创建的是什么类型的queue这里有两个值可选
DISPATCH_QUEUE_SERIAL ( NULL ) 串行queue
DISPATCH_QUEUE_CONCURRENT 并行queue

创建一个串行queue
dispatch_queue_t serial         = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);

   另一个是通过 dispatch_get_global_queue 获取系统默认的queue,这个方法返回queue是并行的

dispatch_queue_t dispatch_get_global_queue( long identifier, unsigned long flags);

参数 identifier 用来指明queue的运行的优先级,可以取下值:

  DISPATCH_QUEUE_PRIORITY_HIGH        高优先级
  DISPATCH_QUEUE_PRIORITY_DEFAULT      默认优先级
  DISPATCH_QUEUE_PRIORITY_LOW             低优先级
  DISPATCH_QUEUE_PRIORITY_BACKGROUND     后台

参数 flags       保留

    另外还可以通过 dispatch_get_main_queue 来获取mian queue。mian queue 是执行在主线程上的一个queue,它与消息循环交错的运行。

    通过调用 dispatch_get_current_queue 可以返回当前的queue。在一个被提交的block的内部调用 dispatch_get_current_queue 函数返回的是运行这个block的queue,在block之外调用返回的是main queue。      

  2. 把block添加到queue运行

    GCD也提供也支持把一个function添加到queue 的api,这个api的特点是多了后缀_f,比如的dispatch_async/dispatch_async_f

    一个block只有被提交到queue后才能被执行,一旦block被提交到queue就无法取消,而且我们是无法得知block执行的状态。所以在一些提交任务后需要取消的场景我们可以用NSOperation。

异步执行block
dispatch_queue_t serial = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL); dispatch_async(serial, ^{ NSLog(@"from queue"); });

dispatch_async 异步的把一个block提交到queue,立即返回
dispatch_sync  同步的把一个block提交到queue,阻塞当前线程直到block执行完成

void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
dispatch_after 在指定的时间到达时异步的提交block到queue执行。
when 参数是指延迟时间,是一个dispatch time 类型,可以通过dispatch_time、dispatch_walltime这两个函数生成

void dispatch_once( dispatch_once_t *predicate, dispatch_block_t block);
dispatch_once 在app的生命周期里只执行一次,常用于创建单例

  3.void dispatch_set_target_queue( dispatch_object_t object, dispatch_queue_t queue);这个函数可以设置dispatch object的目标queue,dispatch object 可以使dispatch queue、dispatch source 、Dispatch I/O channels。目标queue就是dispatch object的finalizer(dispatch source)调用者,修改dispatch object的target queue会修改dispatch object如下的行为:

  当被修改的dispatch object 为 Dispatch queue时,被修改的queue的优先级与target queue的优先级一致。如果修改一个serial queue 的 target queue为另一个serial queue 。为了下面方便描述,我们把被修改的queue叫做queueA,把target queue叫做queueB。那么在set target queue之后,先把一个blockA异步的提交到queueA,再把blockB异步的提交到queueB,这时候blockB会先于blockA执行。文字描述可能有点不清晰,我们直接来看代码

   dispatch_queue_t serial1         = dispatch_queue_create("serial1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t serial2         = dispatch_queue_create("serial2", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serial1, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"blockA");
    });
    dispatch_async(serial2, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"blockA");
    });
    dispatch_set_target_queue(serial1, serial2);
    dispatch_async(serial1, ^{
        NSLog(@"setted target : blockA ");
    });
    dispatch_async(serial2, ^{
        NSLog(@"setted target : blockB");
    });

输出:

2017-01-06 21:02:32.268 GCDTest[7379:1744101] blockA
2017-01-06 21:02:32.268 GCDTest[7379:1744094] blockA
2017-01-06 21:02:32.269 GCDTest[7379:1744094] setted target : blockB
2017-01-06 21:02:32.269 GCDTest[7379:1744094] setted target : blockA

  至于为什么是这个结果我现在还不清楚,我猜测应该是提交到queueA的block,等待它的target queue -> queueB的正在排队的办block都提交后,才把blockA提交到target block -> queueB。

  当被修改的dispatch object 为 Dispatch source时,由新设置的target queue 来响应 Dispatch source的各个handle。

  4.void dispatch_main( void)  这个函数的作用是暂停一下主线程,等待所有被提交到main queue的任务执行完成。一般的通过 UIApplicationMain (iOS), NSApplicationMain (OS X), or CFRunLoopRun 创建的程序是禁止调用这个函数的,所以这个函数我们了解一下即可。

  看代码:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"main queue 1");
    });
    dispatch_main(); // 如果不加这句代码的话,上面提交的block是不会执行的

    return 0;
}

   5.上面提到dispatch_after函数,它的第一个参数需要一个dispatch_time_t类型,由dispatch_time、dispatch_walltime这两个函数生成(也可以取DISPATCH_TIME_NOW/DISPATCH_TIME_FOREVER这两个宏,第一个指现在马上,第二个指永远不)。现在我们来学习一下这两个函数

  首先是dispatch_time_t dispatch_time( dispatch_time_t when, int64_t delta); dispatch_time函数需要输入两个参数

  when 参数是指定一个开始值。一般取DISPATCH_TIME_NOW,意思是说从现在开始吧。

  delta 参数的单位是纳秒,

  dispatch_time 函数的作用就是返回一个相距 when 时间的 delta 个纳秒的时间间隔。比如一个延迟5秒的定时任务可以这样写:

// 从现在开始,5s后开始提交block到mian queue
dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5); dispatch_after(when, dispatch_get_main_queue(), ^{ NSLog(@"do something."); });

  dispatch_walltime函数同dispatch_time函数的功能一样的,只是两者的第一个参数类型不一样 ,前者参考的时间是 wall clock,后者参考时间四default clock。至于default clock 与 wall clock 的区别,可以自行了解。如果可以用一个不那么严谨的解释来描述的话,就如先描述:dispatch_time返回的是相距一个时间间隔(此时此刻)多少纳秒的时间间隔,dispatch_walltime返回的是一个相距特定日期时间(2017-01-08 22:30)多少纳秒的时间间隔。

  下面给出使用dispatch_walltime的一个例子:

    NSDate * date = [NSDate date];
    NSLog(@"date:%@",date); // 2017-01-08 22:30:00
    NSTimeInterval interval = date.timeIntervalSince1970;
    struct timespec time;
    time.tv_nsec = NSEC_PER_SEC * interval;
    time.tv_sec  = 0;
    dispatch_time_t when =  dispatch_walltime(&time,NSEC_PER_SEC*5);
   // when 等于 从2017-01-8 22:30:00开始5s钟后,(2017-01-08 22:30:05 ) dispatch_after(when, dispatch_get_main_queue(),
^{ NSLog(@"do something."); });     

三、dispatch Groups - 组

  通过dispatch groups你可以同步一组blocks,你不需要管blocks执行顺序,在所有被提交到groups的blocks都执行完成后,你会得到通知。整个dispatch_groups 技术涉及3个概念,我们先理一下:

  .block:执行任务的代码块,这个不用多说大家都知道

  .queue:实际运行block代码的线程,上一节已经谈过了

  .qroup:指把一些blocks,人工的组合在一起让它们成为一个组。

  一句话总结dispatch groups技术:

    创建一个group,并把一些blocks(需要指定执行queue)添加进来,等到所有的blocks都执行完后发通知到group。

  我们直接来看代码:  

    dispatch_queue_t serial     = dispatch_queue_create("serial", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group      = dispatch_group_create();
    dispatch_group_async(group, serial, ^{
        NSLog(@"serial block");
    });
    dispatch_group_async(group, concurrent, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"concurrent blockA");
    });
    dispatch_group_async(group, concurrent, ^{
        NSLog(@"concurrent blockB");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"group complete");
    });
结果输出:
2017-01-09 09:58:56.137 GCDTest[9038:1830160] serial block
2017-01-09 09:58:56.137 GCDTest[9038:1830161] concurrent blockB
2017-01-09 09:58:57.142 GCDTest[9038:1830227] concurrent blockA
2017-01-09 09:58:57.142 GCDTest[9038:1830127] group complete

  上面例子中有两个api

  void dispatch_group_async( dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block); 异步的把一个block关联到一个group

  void dispatch_group_notify( dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);把一个complete block提交给group,当group的所有关联blocks都执行完成后,会执行你提交的complete block。如果group中没有关联的block的,会立即执行complete block。

  还有一个同步等待group执行完成通知的API : long dispatch_group_wait( dispatch_group_t group, dispatch_time_t timeout);

  dispatch_group_wait会一直阻塞着,直到group所有关联的blocks都执行完成或者第二个参数提供的超时时间已经超时。

  我们还可以通过这两个下面api来手动管理group的通知

  dispatch_group_enter( dispatch_group_t group);   // count 自增 1 (初始化为0)
  dispatch_group_leave( dispatch_group_t group);  // count 自减 1 (当count 为0时发complete 通知)

  我们直接看代码

    dispatch_queue_t concurrent1 = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t concurrent2 = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group      = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_async(concurrent1, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"concurrent1 block ");
        dispatch_async(concurrent2, ^{
            [NSThread sleepForTimeInterval:1];
            NSLog(@"concurrent2 block  ");
            dispatch_group_leave(group);
        });
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"group complete");
    });

    // 输出:
    // 2017-01-09 10:26:31.752 GCDTest[10009:1858381] concurrent1 block
    // 2017-01-09 10:26:32.756 GCDTest[10009:1858381] concurrent2 block
    // 2017-01-09 10:26:32.756 GCDTest[10009:1858042] group complete

四、dispatch Semaphores - 信号量

  这个比较简单了,就是一个生产者消费者模式。

  首先创建一个信号信号量并指定一个初始化值:

    dispatch_semaphore_t dispatch_semaphore_create( long value);

  当生产出一个产品时,我们增加一下信号量,下面这个函数返回值是当期有多少个可用的产品

    long dispatch_semaphore_signal( dispatch_semaphore_t dsema); 

  当我们需要产品的时候,调用一下wait函数,如果返回0说明有产品可用,非0则相反

    long dispatch_semaphore_wait( dispatch_semaphore_t dsema, dispatch_time_t timeout);

  直接上代码:    

// 生产者
    dispatch_queue_t producer       = dispatch_queue_create("producer", DISPATCH_QUEUE_CONCURRENT);
    // 消费者
    dispatch_queue_t client         = dispatch_queue_create("client", DISPATCH_QUEUE_CONCURRENT);
    // 信号量
    dispatch_semaphore_t carFactory = dispatch_semaphore_create(0);
    dispatch_async(producer, ^{
        for (int i=0; i < 5; i++) {
            [NSThread sleepForTimeInterval:i];
            NSLog(@"[%d]output a car",i+1);
            dispatch_semaphore_signal(carFactory);
        }
    });
    dispatch_async(client, ^{
        while (1) {
            long ret = dispatch_semaphore_wait(carFactory, DISPATCH_TIME_NOW);
            
            if (ret == 0) {
                NSLog(@"get a car");
            }
        }
    });
    /*
    输出
     2017-01-09 11:30:54.754 GCDTest[17921:1948479] [1]output a car
     2017-01-09 11:30:54.755 GCDTest[17921:1948486] get a car
     2017-01-09 11:30:55.759 GCDTest[17921:1948479] [2]output a car
     2017-01-09 11:30:55.760 GCDTest[17921:1948486] get a car
     2017-01-09 11:30:57.764 GCDTest[17921:1948479] [3]output a car
     2017-01-09 11:30:57.765 GCDTest[17921:1948486] get a car
     2017-01-09 11:31:00.767 GCDTest[17921:1948479] [4]output a car
     2017-01-09 11:31:00.767 GCDTest[17921:1948486] get a car
     2017-01-09 11:31:04.770 GCDTest[17921:1948479] [5]output a car
     2017-01-09 11:31:04.770 GCDTest[17921:1948486] get a car
    */

五、dispatch Barriers - 障碍

  通过 dispatch_barrier 允许在并行queue中提交一个barrier block,假设我们把提交动作发生的这个时间点记做A点。完成提交barrier block动作后,会一直等待A点前提交的blocks都执行完毕才去执行barrier block,当barrier block被执行完毕后就开始按照concurrent queue既有的规制运行A点后提交的blocks。有两个提交barrier block的API,一个异步的,一个同步的。直接看代码

dispatch_queue_t concurrent = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrent, ^{
        NSLog(@"block A");
    });
    dispatch_async(concurrent, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"block B");
    });
    // 异步提交
    dispatch_barrier_async(concurrent, ^{
        NSLog(@"barrier block ");
    });
    // 同步提交
    dispatch_barrier_sync(concurrent, ^{
        NSLog(@"barrier block");
    });
    dispatch_async(concurrent, ^{
        NSLog(@"block C");
    });
    // 结果
    2017-01-09 13:03:17.203 GCDTest[19661:2010506] block A
    2017-01-09 13:03:19.206 GCDTest[19661:2010513] block B
    2017-01-09 13:03:19.207 GCDTest[19661:2010513] barrier block
    2017-01-09 13:03:19.208 GCDTest[19661:2010513] block C

  值得注意的是,只能把barrier block提交到自己通过dispatch_queue_crate创建的concurrent queue,如果提交到serial queue、global queue的话,barrier block不会起作用,与dispatch_sync、dispatch_async 无异 

六、dispatch sources - 系统源

  通过dispatch source 可以监控系统的一些事件,当这些事件发生时会把你的block提交到queue执行。包括下列事件

  定时器事件

  描述符(文件)事件

  信号量事件

  进程事件

  在这里我只谈谈定时器怎么用的,其他事件在ios doc都有sample code;

  dispatch source 有一套api可以用,我先来个概念快照

    . dispatch_source_t dispatch_source_create( dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue) 创建一个dispatch source

    . void dispatch_source_cancel( dispatch_source_t source);  取消一个dispatch source

    . void dispatch_source_set_event_handler( dispatch_source_t source, dispatch_block_t handler); 设置event handle block

    . void dispatch_source_set_cancel_handler( dispatch_source_t source, dispatch_block_t cancel_handler); 设置dispatch source 被取消的 handle block

    . void dispatch_source_set_timer( dispatch_source_t source, dispatch_time_t start, uint64_t interval, uint64_t leeway); // 给dispatch source设置一个定时器。

  看过api快照后,你应该大概知道怎么用GCD创建一个定时器了 :先创建一个dispatch source ,然后给它设置 event、cancel handler 的block 和 设置timer属性,最后调用dispatch_resume执行dispatch source就行了。我们在代码里面详细说明  

    __block int count = 0;
    /*
     * dispatch_source_t dispatch_source_create( dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)
     * 第一个参数指明这是一个timer,当然还可以指定其他类型
     * 第二个参数是一个系统资源的句柄,比如文件句柄
     * 第三个参数为flag
     * 第四个参数是handle block被提交到的queue
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    if (timer) {
        // 设置 timer
        dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 1);
        // 设置 event handler
        dispatch_source_set_event_handler(timer, ^{
            NSLog(@"timer fire");
            if (count++ == 4) {
                // 取消dispatch source
                dispatch_source_cancel(timer);
            }
        });
        // 设置 event handler
        dispatch_source_set_cancel_handler(timer, ^{
            NSLog(@"time cancel.");
        });
        // 启动 dispatch source
        // 因为create后还需要配置一些行为,所以需要手动resume dispatch source
        dispatch_resume(timer);
        // 注意:
        // 这里需要保存一下timer到类变量,不然timer是局部变量运行到函数尾部是,这个定时器也就没了
        self.timer = timer;
    }

七、dispatch I/O - I/O

     

八、总结

  最后再来看看dispatch_object的管理:

    //dispatch_object 对象的内存管理,用ARC技术的话,这两个函数就没有用了
    void dispatch_release( dispatch_object_t object);
    void dispatch_retain( dispatch_object_t object);
// 注意 void dispatch_resume( dispatch_object_t object); // counting 减1 等于0就回复 void dispatch_suspend( dispatch_object_t object); // counting 加1 大于0就挂起 // 可以给dispatch_object设置一个context用于在各个任务之间共享 void * dispatch_get_context( dispatch_object_t object); // 获取 context void dispatch_set_context( dispatch_object_t object, void *context); // 设置 context // 可以给dispatch_object设置一个执行完成的回调函数 void dispatch_set_finalizer_f( dispatch_object_t object, dispatch_function_t finalizer);

 

posted @ 2017-01-03 12:38  水谷  阅读(561)  评论(0编辑  收藏  举报