GCD常用方法总结

GCD是异步执行任务的技术支之一,开发者只需要将想要执行的block任务添加到适当的Dispatch Queue(调度队列)里,GCD就能生成必要的线程并计划地执行任务。由于GCD线程管理是作为系统的一部分实现的,因此可统一管理,这样就比以前的线程更有效率。下面的例子代码在这里

Dispatch Queue

Dispatch Queue 是执行任务的等待队列,添加到Dispatch Queue的任务按照FIFO(先进先出)执行处理。GCD里存在两中Dispatch Queue

  • Serial Dispatch Queue: 串行队列。使用一个线程串行执行添加到其中的任务。
  • Concurrent Dispatch Queue: 并行队列。使用多个线程并行执行添加到其中的任务。

获取这两种队列的方法有两种:

  1. 通过dispatch_queue_create(const char *label, <#dispatch_queue_attr_t attr#>)函数可生成Dispatch Queue。第一个参数是给queue指定一个标识,该标识在instrument和崩溃时会显示,推荐使用的格式是 (com.example.myqueue) ,可以设置为NULL。第二个参数设置为DISPATCH_QUEUE_SERIAL是生成Serial Dispatch Queue,设置为DISPATCH_QUEUE_CONCURRENT是生成Concurrent Dispatch Queue。在iOS6之前, Dispatch Queue需要程序员自己去释放,使用dispatch_release()
     dispatch_queue_t queue = dispatch_queue_create("come.example.gcd.mySerialDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
     dispatch_async(queue, ^{
         NSLog(@"fsdfs");
     });
     dispatch_release(queue);
    
    block被提交到queue后,会持有queue的引用计数,直到block完成为止,一旦queue的所有引用都释放后,queue将会被系统dealloc。上面代码中的queue 虽然在添加了block之后立即release了,但是block已经持有了queue,所以等到block执行后queue才会释放。
  2. 获取系统提供的Dispatch Queue。
    系统提供了dispatch_get_main_queue(),这个是主线程,是Serial Dispatch Queue。
    Global Dispatch Queue 是所有应用程序都能够使用的Concurrent Dispatch Queue;需要使用并行队列时,可以不用通过dispatch_queue_create创建Concurrent Dispatch Queue,直接获取Global Dispatch Queue即可。
    dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     dispatch_async(queue1, ^{
         NSLog(@"queue1 block1 %@", [NSThread currentThread]);
     });
     dispatch_async(queue1, ^{
         NSLog(@"queue1 block2 %@", [NSThread currentThread]);
     });
    
    控制台打印的结果
    queue1 block1 <NSThread: 0x60000007fc80>{number = 3, name = (null)}
    queue1 block2 <NSThread: 0x60800026ab80>{number = 5, name = (null)}
    
    queue1这个队列里系统自动生成了两个线程去执行block任务。
    Global Dispatch Queue有四种优先级
    #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
    

Dispatch async 和 sycn

dispatch_asycn函数的"asycn"意味着异步,就是将block追加到指定的queue中后,dispatch_asycn函数立即返回,不会等待执行的结果。
dispatch_sycn函数意味着同步,也就是将block函数追加到queue中后,在block任务执行完之前,dispatch_sycn函数会一直等待。
dispatch_sycn函数使用简单,所以也容易引起死锁问题,如果dispatch_sycn把blcok提交到当前的queue里,并且当前的队列是串行队列,会造成死锁。例如在主线程中执行以下代码就会造成死锁:

 dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"hello world");
 });

在主线程里调用上面的方法,但是又把block提交到了主线程里,主线程要等待dispatch_syn函数返回后才能执行block中的代码,dispatch_sycn函数又要等到主线程执行完block后才能返回,因此造成了死锁。下面的例子也是一样的:

dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
     dispatch_sync(mainQueue, ^{
            NSLog(@"hello world");
        });
 });

在串行队列中也会有同样的问题:

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.serialQueue", NULL);  
  dispatch_async(serialQueue, ^{
        dispatch_sync(serialQueue, ^{
            NSLog(@"hello world");
        });
    });

nslog不会被执行。
在并行队列中使用dispatch_async是不会出现死锁的。

Dispatch Group

经常会碰到这种情况,想要在加入到Dispatch Queue中的多个block任务都执行完后去执行其他任务,如果使用的是串行队列,只要将所有的block任务加入到串行队列并在最后追加其他任务即可;如果使用的是并行队列或者有多个DIspatch Queue时,可以使用Dispatch Group。

  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(@"block1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block3");
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"done");
    });

在上面的示例中dispatch_group_async(<#dispatch_group_t group#>, dispatch_queue_t queue, <#^(void)block#>)是将block提交到queue中,同时将block和group联系起来。
dispatch_group_notify(dispatch_group_t group, <#dispatch_queue_t queue#>, <#^(void)block#>)是等到之前与group联系过的block执行完后会把这个block提交到queue中。
无论向什么样的Dispatch Queue中追加任务,使用Dispatch Group都可以监视这些任务的结束。
另外,在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(@"block1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block2");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"block3");
    });
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

dispatch_group_wait函数会同步地等待与group有联系的block执行完成。
这个函数的第二个参数是等待时间,上面的示例中是永久等待,如果想要指定等待时间:

 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC);
    long result = dispatch_group_wait(group, time);
    if (result == 0) {
        //与group联系的任务已全部执行完
        
    }else{
        //虽然过了指定时间,但还没执行完
    }

Dispatch_barrier_async

在访问数据库或文件时,如果多个读取处理并行操作是不会有问题的,但是写入处理不可以与其他的写入处理以及包含读取处理的其他处理并行执行。也就是说,为了高效访问,读取处理追加到Concurrent Dispatch Queue中,写入处理在任意一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在写入处理没有执行之前,读取处理不可执行)。

 dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        //读取处理block1
    });
    dispatch_async(queue, ^{
        //读取处理block2
    });
    dispatch_async(queue, ^{
        //读取处理block3
    });
    dispatch_async(queue, ^{
        //写入处理block4
    });
    dispatch_async(queue, ^{
        //读取处理block5
    });

如果像上面这样简单地在dispatch_asycn函数中加入写入处理,那么根据Concurrent Dispatch的性质,就有可能先执行了block5的写入处理,然后在写入处理前面的读取处理中就会读取到与期待不符的数据,还可能因非法访问导致应用程序异常结束,如果追加多个写入处理,则可能发生更多问题,比如数据竞争。
因此我们要使用dispatch_barrier_async(<#dispatch_queue_t queue#>, <#^(void)block#>)函数。dispatch_barrier_asycn,调用这个函数会把blcok提交到queue里并且立即返回,不会等待block执行。blcok被添加到queue后,它会等待之前添加的blcok执行完后才开始执行,并且在它之后添加的block会等待它自己执行完后才会执行。

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        //读取处理block1
    });
    dispatch_async(queue, ^{
        //读取处理block2
    });
    dispatch_async(queue, ^{
        //读取处理block3
    });
    dispatch_barrier_async(queue, ^{
        //写入处理block4,等待前三个block执行完后才会执行这一个
    });
    dispatch_async(queue, ^{
        //读取处理block5,等待block4执行完后才会执行这一个
    });

dispath_after

想要在指定时间后将block添加到队列里,可以使用dispatch_after函数来实现。

    dispatch_time_t time = dispatch_time( DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
    
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"wait 3 seconds");
    });

需要注意的是,dispatch_after函数并不是在指定的时间后执行block,而是在指定的时间将block添加到queue中。在上面的代码中,因为主线程在RunLoop中执行,所以在每隔1/60秒执行的RunLoop中,block最快在3s后执行,最慢在3s+1/60s后执行,并且在主线程又大量处理追加或主线程的处理本身有延时,这个时间会更长。
dispatch_after的第一个参数是指定时间用的dispatch_time_t类型的值,该值可以用dispatch_time或dispatch_walltime函数获得。
dispatch_time(<#dispatch_time_t when#>, <#int64_t delta#>)
dispatch_time函数能够获取从第一个参数指定的时间开始,到第二个参数指定的单位时间之后的时间。第一个参数设置为DISPATCH_TIME_NOW表示是从现在的时间开始的。第二个参数是数值和NSEC_TIME_NOW的乘积得到单位为毫微秒的数值。"ull"是C语言的数值字面量,是显示表示类型时使用的字符串(标识“unsinged long long”)。
dispatch_walltime函数由POSIX中使用的struct time spec类型的时间得到dispatch_time_t类型的值。dispatch_time通常用于计算相对时间,dispatch_walltime通常用于计算绝对时间。例如在dispatch_after函数中想指定2016年11月11日11时11分11秒这一绝对时间的情况。
dispatch_walltime(const struct timespec *when, <#int64_t delta#>)
第一个参数是一个结构体指针,我们可以通过NSDate对象操作完成:

dispatch_time_t getDispatchTimeByDate(NSDate * date){
    NSTimeInterval interval;
    double second, subsecond;
    struct timespec timespec;
    dispatch_time_t milestone;
    interval = [date timeIntervalSince1970];
    subsecond = modf(interval, &second);//分解interval,返回只是interval小数部分,second获得整数部分
    timespec.tv_nsec = subsecond * NSEC_PER_SEC;
    milestone = dispatch_walltime(&timespec, 0);
    return milestone;
}

dispatch_apply

dispatch_apply(<#size_t iterations#>, dispatch_queue_t _Nonnull queue, <#^(size_t)block#>)函数是dispatch_sycnDispatch Group的关联API。该函数按指定的次数将指定的block追加到指定的Dispatch Group中,并等待所有的block执行完后函数返回。第一个参数是迭代的次数,第二个参数是指定的queue,第三个参数是指定的block,block里有一个size_t类型的参数,用来区分各个block使用。

 dispatch_queue_t serialQueue = dispatch_queue_create("com.example.serialQueue", DISPATCH_QUEUE_SERIAL);
 dispatch_apply(10, serialQueue, ^(size_t index) {
    NSLog(@"serialQueue %zu, %@", index, [NSThread currentThread]);
  });
  NSLog(@"test1");
[2510:77935] serialQueue 0, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 1, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 2, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 3, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 4, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 5, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 6, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 7, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 8, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] serialQueue 9, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:77935] test1

串行队列里只有一个线程,他会按顺序迭代执行block,并等到所有的block执行完后再执行后面的代码。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"queue %zu, %@", index, [NSThread currentThread]);
 });    
 NSLog(@"test");
[2510:78206] queue 4, <NSThread: 0x60000007d2c0>{number = 8, name = (null)}
[2510:78208] queue 6, <NSThread: 0x61800007d380>{number = 10, name = (null)}
[2510:78205] queue 2, <NSThread: 0x608000076380>{number = 7, name = (null)}
[2510:78207] queue 5, <NSThread: 0x610000262c00>{number = 9, name = (null)}
[2510:78016] queue 0, <NSThread: 0x618000071b80>{number = 5, name = (null)}
[2510:77935] queue 3, <NSThread: 0x608000070dc0>{number = 1, name = main}
[2510:78013] queue 1, <NSThread: 0x600000265e40>{number = 6, name = (null)}
[2510:78209] queue 7, <NSThread: 0x608000079cc0>{number = 11, name = (null)}
[2510:78206] queue 8, <NSThread: 0x60000007d2c0>{number = 8, name = (null)}
[2510:78208] queue 9, <NSThread: 0x61800007d380>{number = 10, name = (null)}
[2510:77935] test

在并发队列里,会根据需要生成必要的线程一步地执行block,dispatch_apply依然要等到所有block执行完后再执行后面的代码。
我们来看一下下面的代码

NSArray * array = @[@"0", @"1", @"2", @"3",@"4", @"5"];
dispatch_apply([array count], dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t index) {
    NSLog(@"index = %zu: %@", index , array[index]);
 });

这样可以简单地在并发队列里对所有元素执行block,不必一个个编写for循环。

由于dispatch_applydispatch_sycn函数相同,会等待处理执行结束,因此推荐在dispatch_asycn函数中非同步地执行dispatch_apply函数。

 NSArray * array = @[@"aa", @"bb", @"cc", @"dd",@"ee", @"rr"];
 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
 dispatch_async(queue, ^{
    dispatch_apply([array count], queue, ^(size_t index) {
        NSLog(@"index == %zu: %@", index, [array objectAtIndex:index]);
     });
    //等到dispatch_apply函数中的处理全部结束
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"done");
    });
 });

Dispatch Semaphore

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSMutableArray * array =  @[].mutableCopy;
    for (int i = 0; i < 100000; i++) {
        dispatch_async(queue, ^{
            [array addObject:[NSNumber numberWithInt:i]];
        });
    }

由于该代码使用Global Dispatch Queue 更新NSMutableArray对象,由于多个线程资源抢夺,所以执行后由于内存错误导致应用程序异常结束的概率非常高。此时应使用Dispatch Semaphore,它可以被当做锁来使用,YYCache中的就使用了dispatch_semaphore处理磁盘缓存的线程安全参考
Dispatch semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。所谓信号,类似于过马路时常用的手旗,可以通过时举起手旗,不可通过时放下手旗。而在Dispatch semaphore,使用计数来实现该功能。计数为0时等待,计数为1或大于1时,减去1而不等待。
通过dispatch_semaphore_create(<#long value#>)函数生成Dispatch Semaphore。参数表示计数的初始值,

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值达到大于或等于1。当计数值大于等于1,或者在等待中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回,第二个参数是由dispatch_time_t类型值指定等待时间。上面代码表示永久等待。
dispatch_semaphore_wait函数返回为0时,可安全地执行需要进行排他控制的处理,该处理结束时通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1.

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    /*
     Dispatch Semaphore的计数初始值设为1,保证访问array的线程同时只有一个。
     */
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    NSMutableArray * array =  @[].mutableCopy;
    for (int i = 0; i < 10000; i++) {
        dispatch_async(queue, ^{
            /*
             当semaphore的计数值大于或等于1时,会将semaphore的计数值减去1,dispatch_semaphore_wait函数会执行返回,即函数返回后semaphore的计数值为0,当其他线程执行block中的代码时,会因为semaphore的计数值为0而处于等待状态,即访问array的线程只有一个,因此可以安全的进行更新             */
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            [array addObject:[NSNumber numberWithInt:i]];
            /*
             将semaphore的计数值加1,如果有通过dispatch_sycn_wait函数等待semaphore计数值增加的线程,就由最先等待的线程执行*/
            dispatch_semaphore_signal(semaphore);
        });
    }

使用信号量实现URLSession同步

NSUrlSession的方法全是异步的,要想实现同步的,也可以使用信号量来实现。

NSURLSession * session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
 NSURLSessionDataTask * data = [session dataTaskWithURL:[NSURL URLWithString:@"http://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"222222");
        dispatch_semaphore_signal(sem);

 }];
 [data resume];
 dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
 NSLog(@"333333");

由信号量于初始值是0,调用dispatch_semaphore_wait后信号量值会小于0,这时候dispatch_semaphore_wait不会立即返回,它会等待dispatch_semaphore_signal后才会返回。因此会先打印222222,之后才会打印333333

使用信号量控制并行队列的线程数

使用信号量控制并行队列的线程数

dispatch_suspend / dispatch_resume

当追加大量处理到Dispatch Queue 时,在追加处理的过程中,有时希望不执行已追加的处理。例如盐酸结果被block截获时,一些处理会对这个演算结果造成影响。
在这种情况下,只要挂起Dispatch Queue即可,当可执行的时候再恢复。
dispatch_suspend函数挂起指定的Dispatch Queue。
dispatch_suspend(queue);
dispatch_resume函数恢复指定的Dispatch Queue。
dispatch_resume (queue);
这些函数对已经执行的处理没有影响,挂起后,追加到Dispatch Queue中但还没有执行的处理在暂停执行,而恢复则会使这些处理能够继续执行。

dispatch_once

dispatch_once函数是保证在应用程序中只执行一次指定处理的API。

+ (CustomModel *)shareInstance{
     static CustomModel * shateInstance = nil;
    static dispatch_once_t onceToken;
    NSLog(@"first == %ld",onceToken);
    dispatch_once(&onceToken, ^{
        shateInstance = [[CustomModel alloc] init];
    });
    NSLog(@"second == %ld",onceToken);
    NSLog(@"-----------------");
    return shateInstance;
}

使用dispatch_once函数,该源代码即使在多线程环境下执行,也可保证百分百安全。
dispatch_once函数的原型是void _dispatch_once(dispatch_once_t *predicate, DISPATCH_NOESCAPE dispatch_block_t block),这个函数的作用是使得block在整个程序的生命周期中只执行一次,每次调用这块代码时通过predicate来检查,在这里predicate必须严格的初始化为0
可以测试线面的输出:

 [CustomModel shareInstance];
 [CustomModel shareInstance];
 [CustomModel shareInstance];

[19923:1187470] first == 0
[19923:1187470] second == -1
[19923:1187470] -----------------
[19923:1187470] first == -1
[19923:1187470] second == -1
[19923:1187470] -----------------
[19923:1187470] first == -1
[19923:1187470] second == -1
[19923:1187470] -----------------

若onceToken被初始化为0,那么在调用dispatch_once函数时检测到其值为0,就执行block,执行完后onceToken减一。下次调用dispatch_once函数时检测到onceToken = -1,将不会执行block。

Dispatch Sources

iOS 中有两种 Source,一种是 Run Loop Source ,一种是 Dispatch Source。
Source 可以理解为产生事件的地方,Source 产生事件,然后 Source 的回调函数负责 处理这些事件。
Dispatch Source 也会产生一些特定的事件,当这些事件发生的时候,其回调的 block 会自动加入到 对应的 dispatch queue 中。

使用dispatch_source_create创建的源默认是悬停(suspended)的,必须要使用dispatch_resume来开启事件的传递。
Dispatch Sources的类型:

  • DISPATCH_SOURCE_TYPE_DATA_ADD 用户自定义的事件-变量相加
  • DISPATCH_SOURCE_TYPE_DATA_OR 用户自定义的事件-变量相或
  • DISPATCH_SOURCE_TYPE_MACH_RECV Mach 端口接收事件
  • DISPATCH_SOURCE_TYPE_MACH_SEND Mach 端口发送事件
  • DISPATCH_SOURCE_TYPE_PROC 与进程相关的事件
  • DISPATCH_SOURCE_TYPE_READ 文件可读
  • DISPATCH_SOURCE_TYPE_SIGNAL 接收到 UNIX 信号
  • DISPATCH_SOURCE_TYPE_TIMER 定时器
  • DISPATCH_SOURCE_TYPE_VNODE 文件系统有变更
  • DISPATCH_SOURCE_TYPE_WRITE 文件可写
  • DISPATCH_SOURCE_TYPE_MEMORYPRESSURE
    这些事件都是来自于 XNU 内核中,kqueue 是用来处理这些事件的最好的一种方法,Dispatch Source 就是对 kqueue 的封装。

使用的 Dispatch Source 而不使用 dispatch_async 的唯一原因就是利用联结的优势。
联结的大致流程:在任一线程上调用函数 dispatch_source_merge_data 后,会把Dispatch Source回调的block提交到关联的队列中。dispatch_source_merge_data这个函数有一个参数是unsigned long value,当Dispatch Source的类型是DISPATCH_SOURCE_TYPE_DATA_ADD,事件在连接会把这些数字相加;当Dispatch Source的类型是DISPATCH_SOURCE_TYPE_DATA_OR时,事件在连接会把这些数字逻辑与运算。当block执行时,我们可以使用dispatch_source_get_data函数访问当前值,然后这个值会被重置为0。

假设一些异步执行的代码会更新一个进度条,我们可以将UI更新工作push到主线程中。然而,这些事件可能会有一大堆,我们不想对UI进行频繁而累赘的更新,理想的情况是当主线程繁忙时将所有的改变联结起来。
用dispatch source就完美了,使用DISPATCH_SOURCE_TYPE_DATA_ADD,我们可以将工作拼接起来,然后主线程可以知道从上一次处理完事件到现在一共发生了多少改变,然后将这一整段改变一次更新至进度条。

 // 指定源的类型DISPATCH_SOURCE_TYPE_DATA_ADD。设定Main Dispatch Queue 为追加处理的Dispatch Queue
 _source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());
 __block NSUInteger totalComplete = 0;
dispatch_source_set_event_handler(_source, ^{
   //当处理事件被最终执行时,计算后的数据可以通过dispatch_source_get_data来获取。这个数据的值在每次dispatch_source_get_data会被重置,所以totalComplete的值是最终累积的值。
    NSUInteger value = dispatch_source_get_data(_source);
    totalComplete += value;
    NSLog(@"进度:%@", @((CGFloat)totalComplete/100));
 });
  //恢复源
 dispatch_resume(_source);
    
 //恢复源后,就可以通过dispatch_source_merge_data向Dispatch Source(分派源)发送事件:
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        for (NSUInteger index = 0; index < 100; index++) {
            dispatch_source_merge_data(_source, 1);
            usleep(20000);//0.02秒
        }
    });

从log可以看出,dispatch_source_merge_data调用了100次,但是变量event handler block并没有执行100次, 这是因为对应的队列在接收到 Source 事件之后,假如队列处于空闲状态,就会执行对应的回调 Block,假如队列处于 busy 状态,该事件就会和后面的一系列同种事件通过一定的方式被合并起来(也就是把unsigned long value加在一起),等到队列空闲的时候再执行block。

Dispatch Source Timer

利用 Dispatch Source 的 DISPATCH_SOURCE_TYPE_TIMER 类型,我们可以创建一个 跨线程的 定时器(我们平时使用的 NSTimer 是基于 Run Loop 的 timer 事件,只能在对应的线程里触发)

 dispatch_queue_t queue = dispatch_get_main_queue();
 self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);//创建一个 timer;
 dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);//配置 timer,从现在起,每两秒在主线程触发一次,精度为0s
dispatch_source_set_event_handler(self.timer, ^{
    NSLog(@"%ld", self.count++);
 });//timer 触发之后的回调 block
 dispatch_resume(self.time); //启动 timer

有一个使用Dispatch Source Timer来解决输入框频繁输入字符的问题的GCDThrottle
GCDThrottle有两种类型:

typedef NS_ENUM(NSInteger, GCDThrottleType) {
    GCDThrottleTypeDelayAndInvoke,/**< Throttle will wait for [threshold] seconds to invoke the block, when new block comes, it cancels the previous block and restart waiting for [threshold] seconds to invoke the new one. */
    GCDThrottleTypeInvokeAndIgnore,/**< Throttle invokes the block at once and then wait for [threshold] seconds before it can invoke another new block, all block invocations during waiting time will be ignored. When [threshold] seconds passed by, it invokes the last ignored block (if have). */
};

GCDThrottleTypeDelayAndInvoke对应的代码:

NSMutableDictionary *scheduledSources = self.scheduledSourcesForMode0;
dispatch_source_t source = scheduledSources[key];
if (source) {
   dispatch_source_cancel(source);
}
source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, threshold * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 0);
dispatch_source_set_event_handler(source, ^{
       block();
       dispatch_source_cancel(source);
        [scheduledSources removeObjectForKey:key];
 });
dispatch_resume(source);
scheduledSources[key] = source;

从代码可以看出,block会在延时threshold时间后执行,如果在此期间重新调用了这个方法,会把之前的source取消掉,这样之前的block也不会执行。新的block会重新在threshold时间后执行。

GCDThrottleTypeInvokeAndIgnore对应的代码:

NSMutableDictionary *scheduledSources = self.scheduledSourcesForMode1;
NSMutableDictionary *blocksToInvoke = self.blocksToInvoke;
blocksToInvoke[key] = [block copy];
dispatch_source_t source = scheduledSources[key];
    if (source) { return; }
    source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(source, dispatch_time(DISPATCH_TIME_NOW, 0), threshold * NSEC_PER_SEC, 0);
    dispatch_source_set_event_handler(source, ^{
    GCDThrottleBlock blockToInvoke = blocksToInvoke[key];
    if (blockToInvoke) {
        blockToInvoke();
         [blocksToInvoke removeObjectForKey:key];
      } else {
           dispatch_source_cancel(source);
            [scheduledSources removeObjectForKey:key];
       }
    });
dispatch_resume(source);
scheduledSources[key] = source;

从代码可以看出,由于使用了DISPATCH_TIME_NOW,当source resume后,block会立即执行,而source并没有被取消,之后再调用这个方法时都会直接返回:

(if (source) { return; })

等到设置的threshold时间到后才会取消源

dispatch_source_cancel(source);
[scheduledSources removeObjectForKey:key];
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • 章节目录 什么是GCD? 如何在多条路径中执行CPU命令列? 即使多线程存在很多问题(如数据竞争、死锁、线程过多消...
    DrunkenMouse阅读 830评论 1 13
  • 背景 担心了两周的我终于轮到去医院做胃镜检查了!去的时候我都想好了最坏的可能(胃癌),之前在网上查的症状都很相似。...
    Dely阅读 9,178评论 21 42
  • 一. 重点: 1.dispatch_queue_create(生成Dispatch Queue) 2.Main D...
    BestJoker阅读 1,544评论 2 2
  • 一、GCD的API 1. Dispatch queue 在执行处理时存在两种Dispatch queue: 等待现...
    doudo阅读 482评论 0 0
  • 1 宋望不姓宋,姓李。宋望只是他的小名。说起来,这名字还是我妈给取的。宋望妈和我妈娘家是一个地方的,同宗同族,论起...
    何鹏在简书阅读 777评论 1 7