关于 GCD 开发的一些事儿

在之前我们介绍过 NSOperation 的一些东西,这次我们来聊一聊另一个 iOS 开发最经常使用的技术之一 — GCD,GCD 将线程的管理移到系统级别,你只需要定义好要执行的任务,然后丢到合适的 Dispatch queue,GCD 会负责创建线程来执行你的代码,由于这部分是处于系统级别,所以执行的性能通常非常高。GCD 这部分代码苹果已开源,有兴趣的可以去下载了解一下:地址
在介绍 GCD 之前我们先了解一下 Quality of Service:

Quality of Service(QoS)

这是在 iOS8 之后提供的新功能,苹果提供了几个 Quality of Service 枚举来使用:user interactive, user initiated, utility 和 background,通过这告诉系统我们在进行什么样的工作,然后系统会通过合理的资源控制来最高效的执行任务代码,其中主要涉及到 CPU 调度的优先级、IO 优先级、任务运行在哪个线程以及运行的顺序等等,我们通过一个抽象的 Quality of Service 参数来表明任务的意图以及类别。

  • NSQualityOfServiceUserInteractive
    与用户交互的任务,这些任务通常跟 UI 级别的刷新相关,比如动画,这些任务需要在一瞬间完成
  • NSQualityOfServiceUserInitiated
    由用户发起的并且需要立即得到结果的任务,比如滑动 scroll view 时去加载数据用于后续 cell 的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成
  • NSQualityOfServiceUtility
    一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载的任务,这些任务可能花费几秒或者几分钟的时间
  • NSQualityOfServiceBackground
    这些任务对用户不可见,比如后台进行备份的操作,这些任务可能需要较长的时间,几分钟甚至几个小时
  • NSQualityOfServiceDefault
    优先级介于 user-initiated 和 utility,当没有 QoS 信息时默认使用,开发者不应该使用这个值来设置自己的任务

Qos 可以跟 GCD queue 做个对照:

null

对照表

下面我们了解一下 GCD 的一些用法:

Dispatch Queue

开发者将需要执行的任务添加到合适的 Dispatch Queue 中即可,Dispatch Queue 会根据任务添加的顺序先到先执行,其中有以下几种队列:

  • main dispatch queue
    功能跟主线程一样,通过 dispatch_get_main_queue() 来获取,提交到 main queue 的任务实际上都是在主线程执行的,所以这是一个串行队列
  • global dispatch queues
    系统给每个应用提供四个全局的并发队列,这四个队列分别有不同的优先级:高、默认、低以及后台,用户不能去创建全局队列,只能根据优先级去获取:
dispatch_queue_t queue ; 
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
  • user create queue
    用户可以通过 dispatch_queue_create 自己创建队列,该函数有两个参数,第一个是队列的名称,在 debug 的时候方便区分;第二个是队列的一些属性,NULL 或者 DISPATCH_QUEUE_SERIAL 创建出来的队列是串行队列,如果传递 DISPATCH_QUEUE_CONCURRENT 则为并行队列。
//创建并行队列
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", DISPATCH_QUEUE_CONCURRENT); 
  • 队列优先级

dispatch_queue_create 创建队列的优先级跟 global dispatch queue 的默认优先级一样,假如我们需要设置队列的优先级,可以通过 dispatch_queue_attr_make_with_qos_class 或者 dispatch_set_target_queue 方法;

//指定队列的QoS类别为QOS_CLASS_UTILITY
dispatch_queue_attr_t queue_attr = dispatch_queue_attr_make_with_qos_class (DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY,-1);
dispatch_queue_t queue = dispatch_queue_create("queue", queue_attr); 

dispatch_set_target_queue 的第一个参数为要设置优先级的 queue, 第二个参数是对应的优先级参照物

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.MyQueue",NULL);  
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);  
 
//serialQueue现在的优先级跟globalQueue的优先级一样
dispatch_set_target_queue(serialQueue, globalQueue); 
  • dispatch_set_target_queue
    dispatch_set_target_queue 除了能用来设置队列的优先级之外,还能够创建队列的层次体系,当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的 target 指向新创建的队列即可,比如

null

队列体系.png

 dispatch_queue_t targetQueue = dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL);
  dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
  dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
  dispatch_set_target_queue(queue1, targetQueue);
  dispatch_set_target_queue(queue2, targetQueue);
  dispatch_async(queue1, ^{
        NSLog(@"do job1");
        [NSThread sleepForTimeInterval:3.f];
    });
  dispatch_async(queue2, ^{
        NSLog(@"do job2");
        [NSThread sleepForTimeInterval:2.f];
    });
  dispatch_async(queue2, ^{
        NSLog(@"do job3");
        [NSThread sleepForTimeInterval:1.f];
    }); 

可以看到执行的结果如下,这些队列会同步的执行任务。

 GCDTests[13323:569147] do job1
 GCDTests[13323:569147] do job2
 GCDTests[13323:569147] do job3 
  • dispatch_barrier_async
    dispatch_barrier_async 用于等待前面的任务执行完毕后自己才执行,而它后面的任务需等待它完成之后才执行。一个典型的例子就是数据的读写,通常为了防止文件读写导致冲突,我们会创建一个串行的队列,所有的文件操作都是通过这个队列来执行,比如 FMDB,这样就可以避免读写冲突。不过其实这样效率是有提升的空间的,当没有更新数据时,读操作其实是可以并行进行的,而写操作需要串行的执行,如何实现呢:
dispatch_queue_t queue = dispatch_queue_create("Database_Queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        NSLog(@"reading data1");
    });
    dispatch_async(queue, ^{
        NSLog(@"reading data2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"writing data1");
        [NSThread sleepForTimeInterval:1];
        
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"reading data3");
    }); 

执行结果如下:

GCDTests[13360:584316] reading data2
GCDTests[13360:584317] reading data1
GCDTests[13360:584317] writing data1
GCDTests[13360:584317] reading data3 

我们将写数据的操作放在 dispatch_barrier_async 中,这样能确保在写数据的时候会等待前面的读操作完成,而后续的读操作也会等到写操作完成后才能继续执行,提高文件读写的执行效率。

  • dispatch_queue_set_specific 、dispatch_get_specific

这两个 API 类似于 objc_setAssociatedObject 跟 objc_getAssociatedObject,FMDB 里就用到这个来防止死锁,来看看 FMDB 的部分源码

static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
//创建一个串行队列来执行数据库的所有操作
 _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);

 //通过key标示队列,设置context为self
 dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL); 

当要执行数据库操作时,如果在 queue 里面的 block 执行过程中,又调用了 indatabase 方法,需要检查是不是同一个 queue,因为同一个 queue 的话会产生死锁情况

- (void)inDatabase:(void (^)(FMDatabase *db))block {
    FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
    assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
} 
  • dispatch_apply
    dispatch_apply 类似一个 for 循环,会在指定的 dispatch queue 中运行 block 任务 n 次,如果队列是并发队列,则会并发执行 block 任务,dispatch_apply 是一个同步调用,block 任务执行 n 次后才返回。
    简单的使用方法:
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);
//并发的运行一个block任务5次
dispatch_apply(5, queue, ^(size_t i) {
    NSLog(@"do a job %zu times",i+1);
});
NSLog(@"go on"); 

输出结果:

GCDTests[10029:760640] do a job 2 times
GCDTests[10029:760640] do a job 1 times
GCDTests[10029:760640] do a job 3 times
GCDTests[10029:760640] do a job 5 times
GCDTests[10029:760640] do a job 4 times
GCDTests[10029:760640] go on 

在某些场景下使用 dispatch_apply 会对性能有很大的提升,比如你的代码需要以每个像素为基准来处理计算 image 图片。同时 dispatch apply 能够避免一些线程爆炸的情况发生(创建很多线程)

//危险,可能导致线程爆炸以及死锁
for (int i = 0; i < 999; i++){
   dispatch_async(q, ^{...});
}
dispatch_barrier_sync(q, ^{});

// 较优选择, GCD 会管理并发
dispatch_apply(999, q, ^(size_t i){...}); 

Dispatch Block

添加到 gcd 队列中执行的任务是以 block 的形式添加的,block 封装了需要执行功能,block 带来的开发效率提升就不说了,gcd 跟 block 可以说是一对好基友,能够很好的配合使用。

  • 创建 block
    我们可以自己创建 block 并添加到 queue 中去执行
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
//创建block
dispatch_block_t block = dispatch_block_create(0, ^{
        NSLog(@"do something");
    });
dispatch_async(queue, block); 

在创建 block 的时候我们也可以通过设置 QoS,指定 block 对应的优先级,在 dispatch_block_create_with_qos_class 中指定 QoS 类别即可:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
        NSLog(@"do something with QoS");
    });
dispatch_async(queue, block); 
  • dispatch_block_wait
    当需要等待前面的任务执行完毕时,我们可以使用 dispatch_block_wait 这个接口,设置等待时间 DISPATCH_TIME_FOREVER 会一直等待直到前面的任务完成:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block = dispatch_block_create(0, ^{
    NSLog(@"before sleep");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"after sleep");
});
dispatch_async(queue, block);
//等待前面的任务执行完毕
dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
NSLog(@"coutinue"); 

程序运行结果:

GCDTests[16679:863641] before sleep
GCDTests[16679:863641] after sleep
GCDTests[16679:863529] coutinue 
  • dispatch_block_notify
    dispatch_block_notify 当观察的某个 block 执行结束之后立刻通知提交另一特定的 block 到指定的 queue 中执行,该函数有三个参数,第一参数是需要观察的 block,第二个参数是被通知 block 提交执行的 queue,第三参数是当需要被通知执行的 block,函数的原型:
void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue,
        dispatch_block_t notification_block); 

具体使用的方法:

 dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
    dispatch_block_t previousBlock = dispatch_block_create(0, ^{
        NSLog(@"previousBlock begin");
        [NSThread sleepForTimeInterval:1];
        NSLog(@"previousBlock done");
    });
    dispatch_async(queue, previousBlock);
    dispatch_block_t notifyBlock = dispatch_block_create(0, ^{
        NSLog(@"notifyBlock");
    });
    //当previousBlock执行完毕后,提交notifyBlock到global queue中执行
    dispatch_block_notify(previousBlock, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), notifyBlock); 

运行结果:

GCDTests[17129:895673] previousBlock begin
GCDTests[17129:895673] previousBlock done
GCDTests[17129:895673] notifyBlock 
  • dispatch_block_cancel
    之前在介绍 nsopreration 的时候提到它的一个优点是可以取消某个 operation,现在在 iOS8 之后,提交到 gcd 队列中的 dispatch block 也可取消了,只需要简单的调用 dispatch_block_cancel 传入想要取消的 block 即可:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
    NSLog(@"block1 begin");
    [NSThread sleepForTimeInterval:1];
    NSLog(@"block1 done");
});
dispatch_block_t block2 = dispatch_block_create(0, ^{
    NSLog(@"block2 ");
});
dispatch_async(queue, block1);
dispatch_async(queue, block2);
dispatch_block_cancel(block2); 

可以看到如下的执行结果,block2 不再执行了。

GCDTests[17271:902981] block1 begin
GCDTests[17271:902981] block1 done 

Dispatch Group

当我们想在 gcd queue 中所有的任务执行完毕之后做些特定事情的时候,也就是队列的同步问题,如果队列是串行的话,那将该操作最后添加到队列中即可,但如果队列是并行队列的话,这时候就可以利用 dispatch_group 来实现了,dispatch_group 能很方便的解决同步的问题。dispatch_group_create 可以创建一个 group 对象,然后可以添加 block 到该组里面,下面看下它的一些用法:

  • dispatch_group_wait
    dispatch_group_wait 会同步地等待 group 中所有的 block 执行完毕后才继续执行, 类似于 dispatch barrier
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
//将任务异步地添加到group中去执行
dispatch_group_async(group,queue,^{ NSLog(@"block1"); });
dispatch_group_async(group,queue,^{ NSLog(@"block2"); });
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
NSLog(@"go on"); 

执行结果如下,只有 block1 跟 block2 执行完毕后才会执行 dispatch_group_wait 后面的内容。

GCDTests[954:41031] block2
GCDTests[954:41032] block1
GCDTests[954:40847] go on 
  • dispatch_group_notify
    功能与 dispatch_group_wait 类似,不过该过程是异步的,不会阻塞该线程,dispatch_group_notify 有三个参数
void dispatch_group_notify(dispatch_group_t group, //要观察的group
                           dispatch_queue_t queue,   //block执行的队列
                           dispatch_block_t block);   //当group中所有任务执行完毕之后要执行的block 

简单的示意用法:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue,^{ NSLog(@"block1"); });
dispatch_group_async(group,queue,^{ NSLog(@"block2"); });
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"done");
});
NSLog(@"go on"); 

可以看到如下的执行结果

GCDTests[1046:45104] go on
GCDTests[1046:45153] block1
GCDTests[1046:45152] block2
GCDTests[1046:45104] done 
  • dispatch_group_enter dispatch_group_leave
    假如我们不想使用 dispatch_group_async 异步的将任务丢到 group 中去执行,这时候就需要用到 dispatch_group_enter 跟 dispatch_group_leave 方法,这两个方法要配对出现,以下这两种方法是等价的:
dispatch_group_async(group, queue, ^{ 
}); 

等价于

dispatch_group_enter(group);
dispatch_async(queue, ^{
  dispatch_group_leave(group);
}); 

简单的使用方法,可以自己试试没有写 dispatch_group_leave 会发生什么。

dispatch_group_t group = dispatch_group_create();
for (int i =0 ; i<3; i++) {
    dispatch_group_enter(group);
    NSLog(@"do block:%d",i);
    dispatch_group_leave(group);
}
//等待上面的任务完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"go on"); 

Dispatch Semaphore

dispatch semaphore 也是用来做解决一些同步的问题,dispatch_semaphore_create 会创建一个信号量,该函数需要传递一个信号值,dispatch_semaphore_signal 会使信号值加 1,如果信号值的大小等于 1,dispatch_semaphore_wait 会使信号值减 1,并继续往下走,如果信号值为 0,则等待。

//创建一个信号量,初始值为0
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"do some job");
    sleep(1);
    NSLog(@"increase the semaphore");
    dispatch_semaphore_signal(sema); //信号值加1
});
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);//等待直到信号值大于等1
NSLog(@"go on"); 

执行结果如下:

GCDTests[1394:92383] do some job
GCDTests[1394:92383] increase the semaphore
GCDTests[1394:92326] go on 

Dispatch Timer

dispatch timer 通常配合 dispatch_after 使用,完成一些延时的任务:

//延迟5秒后执行任务
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"do job afer 5 seconds");
}); 

Dispatch IO

当我们要读取一份较大文件的时候,多个线程同时去读肯定比一个线程去读的速度要快,要实现这样的功能可以通过 dispatch io 跟 dispatch data 来实现,通过 dispatch io 去读文件时,会使用 global dispatch queue 将一个文件按照一个指定的分块大小同时去读取数据,类似于:

dispatch_async(queue, ^{/* 读取0-99字节 */});
dispatch_async(queue, ^{/* 读取100-199字节 */});
dispatch_async(queue, ^{/* 读取200-299字节 */});
... 

将文件分成一块一块并行的去读取,读取的数据通过 Dispatch Data 可以更为简单地进行结合和分割 。

  • dispatch_io_create
    生成 Dispatch IO, 指定发生错误时用来执行处理的 Block, 以及执行该 Block 的 Dispatch Queue
  • dispatch_io_set_low_water
    设定一次读取的大小(分割的大小)
  • dispatch_io_read
    使用 Global Dispatch Queue 开始并列读取,当每个分割的文件块读取完毕时,会将含有文件数据的 dispatch data 返回到 dispatch_io_read 设定的 block,在 block 中需要分析传递过来的 dispatch data 进行合并处理

可以看下苹果的系统日志 API(Libc-763.11 gen/asl.c) 的源代码使用到了 dispatch IO:源码地址

 //dispatch_io_create出错时handler执行的队列
pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
    //出错时执行的handler
    close(fd);
});
*out_fd = fdpair[1];

//设定一次读取的大小(分割大小)
dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
    if (error)
        return;
    if (err == 0)
    {
        //每次读取到数据进行数据的处理
        size_t len = dispatch_data_get_size(pipedata);
        if (len > 0)
        {
            const char *bytes = NULL;
            char *encoded;
            uint32_t eval;
            dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
            encoded = asl_core_encode_buffer(bytes, len);
            asl_msg_set_key_val(aux, ASL_KEY_AUX_DATA, encoded);
            free(encoded);
            eval = _asl_evaluate_send(NULL, (aslmsg)aux, -1);
            _asl_send_message(NULL, eval, aux, NULL);
            asl_msg_release(aux);
            dispatch_release(md);
        }
    }
    if (done)
    {
        //并发读取完毕
        dispatch_semaphore_signal(sem);
        dispatch_release(pipe_channel);
        dispatch_release(pipe_q);
    }
}); 

假如你的数据文件比较大,可以考虑采用 dispatch IO 的方式来提高读取的速率。

Dispatch Source

dispatch 框架提供一套接口用于监听系统底层对象 (如文件描述符、Mach 端口、信号量等),当这些对象有事件产生时会自动把事件的处理 block 函数提交到 dispatch 队列中执行,这套接口就是 Dispatch Source API,Dispatch Source 其实就是对 kqueue 功能的封装,可以去查看 dispatch_source 的 c 源码实现 (什么是 kqueue?Google,什么是 Mach 端口? Google Again),Dispatch Source 主要处理以下几种事件:

DISPATCH_SOURCE_TYPE_DATA_ADD   变量增加
DISPATCH_SOURCE_TYPE_DATA_OR    变量OR
DISPATCH_SOURCE_TYPE_MACH_SEND  Mach端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV  Mach端口接收
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存压力情况变化
DISPATCH_SOURCE_TYPE_PROC       与进程相关的事件
DISPATCH_SOURCE_TYPE_READ       可读取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL     接收信号
DISPATCH_SOURCE_TYPE_TIMER      定时器事件
DISPATCH_SOURCE_TYPE_VNODE      文件系统变更
DISPATCH_SOURCE_TYPE_WRITE      可写入文件映像 

当有事件发生时,dispatch source 自动将一个 block 放入一个 dispatch queue 执行。

  • dispatch_source_create
    创建一个 dispatch source,需要指定事件源的类型,handler 的执行队列,dispatch source 创建完之后将处于挂起状态。此时 dispatch source 会接收事件,但是不会进行处理,你需要设置事件处理的 handler,并执行额外的配置;同时为了防止事件堆积到 dispatch queue 中,dispatch source 还会对事件进行合并,如果新事件在上一个事件处理 handler 执行之前到达,dispatch source 会根据事件的类型替换或者合并新旧事件。

  • dispatch_source_set_event_handler
    给指定的 dispatch source 设置事件发生的处理 handler

  • dispatch_source_set_cancel_handler
    给指定的 dispatch source 设置一个取消处理 handler,取消处理 handler 会在 dispatch soruce 释放之前做些清理工作,比如关闭文件描述符:

dispatch_source_set_cancel_handler(mySource, ^{ 
   close(fd); //关闭文件秒速符 
}); 
  • dispatch_source_cancel
    异步地关闭 dispatch source,这样后续的事件发生时不去调用对应的事件处理 handler,但已经在执行的 handler 不会被取消。

很多第三方库会用到 dispatch source 的功能,比如著名的 IM 框架 XMPPFramework 在涉及到定时器的时候都采用这种方法,比如发送心跳包的时候 (setupKeepAliveTimer)。
一个简单的例子:

//如果dispatch source是本地变量,会被释放掉,需要这么声明
@property (nonatomic)dispatch_source_t timerSource;

//事件handler的处理队列
dispatch_queue_t queue = dispatch_queue_create("myqueue", NULL);

//
_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

//定时器间隔时间
uint64_t interval = 2 * NSEC_PER_SEC;
//设置定时器信息
dispatch_source_set_timer(_timerSource,DISPATCH_TIME_NOW, interval , 0);

//设置事件的处理handler
dispatch_source_set_event_handler(_timerSource, ^{
    NSLog(@"receive time event");
    //if (done) 
    //   dispatch_source_cancel(_timerSource); 
});
//开始处理定时器事件,dispatch_suspend暂停处理事件
dispatch_resume(_timerSource); 

定时器还可以通过 NSTimer 实现,不过 NSTimer 会跟 runloop 关联在一起,主线层默认有一个 runloop,假如你 nstimer 是运行在子线程,就需要自己手动开启一个 runloop,而且 nstimer 默认是在 NSDefaultRunLoopMode 模式下的,所以当 runloop 切换到其它模式 nstimer 就不会运行,需要手动将 nstimer 添加到 NSRunLoopCommonModes 模式下;而 dispatch source timer 不跟 runloop 关联,所以有些场景可以使用这种方法。

本文总结了 GCD 的一些用法,不过有些 API 可能 iOS8 之后才可以用,如有还有什么可以补充的,欢迎提出~

部分参考

作者:树下老男孩
链接:https://www.jianshu.com/p/f9e01c69a46f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。