GCD基本用法大全

GCD是Apple开发的一个多核编程的解决方案,他可以用于多核的并行运算,并自动利用更多的CPU内核,同时GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程),而且我们使用GCD时,只需要调用GCD的API来填入需要执行的任务,而不需要编写任何线程管理的代码

一、任务和队列

任务:

执行的操作,任务分为同步执行和异步执行两种。

. 同步(sync):一直在当前线程中执行,而不开辟新的线程

. 异步(async):不同的任务开辟新的线程在各自线程内执行 

队列

队列:存放任务的队列,队列是一种特殊的线性表,遵从FIFO(First in first out,先进先出)原则,分为两种:串行队列和并行队列 

. 并行队列(concurrent):多个任务同时执行(每个任务在不同的线程中执行),API为dispatch_async这类函数

. 串行队列(serial):任务按照顺序执行(各个任务可能在同一个线程,也可能在不同的线程),API为dispatch_sync这类函数  

二、GCD四种基础操作

1.串行队列 + 同步执行

- (void)dispatchSyncSerial{   

//这个函数创建队列,第一个参数为队列名,第二个参数标记创建的队列是串行队列还是并行队列,使用两个宏定义来创建,DISPATCH_QUEUE_SERIAL为串行队列,DISPATCH_QUEUE_CONCURRENT为并行队列   

dispatch_queue_t queue = dispatch_queue_create("串行队列名", DISPATCH_QUEUE_SERIAL);       

dispatch_sync(queue, ^{       

sleep(1);       

NSLog(@"在串行队列中执行第一个任务,线程:%@",[NSThread currentThread]);   

});   

dispatch_sync(queue, ^{       

NSLog(@"在串行队列中执行第二个任务,线程:%@",[NSThread currentThread]);  

});  

dispatch_sync(queue, ^{       

sleep(2);       

NSLog(@"在串行队列中执行第三个任务,线程:%@",[NSThread currentThread]);  

});   

dispatch_sync(queue, ^{       

NSLog(@"在串行队列中执行第四个任务,线程:%@",[NSThread currentThread]);   

});}

/* 结果

2017-08-22 18:42:55.397 GCD简介与Demo[4463:266445] 在串行队列中执行第一个任务,线程:{number = 1, name = main}

2017-08-22 18:42:55.398 GCD简介与Demo[4463:266445] 在串行队列中执行第二个任务,线程:{number = 1, name = main}

2017-08-22 18:42:57.399 GCD简介与Demo[4463:266445] 在串行队列中执行第三个任务,线程:{number = 1, name = main}

2017-08-22 18:42:57.400 GCD简介与Demo[4463:266445] 在串行队列中执行第四个任务,线程:{number = 1, name = main}

*/

结论:串行队列+同步执行,任务按照添加的顺序,依次执行,并且都在同一个线程中执行

2.串行队列 + 异步执行

- (void)dispatchAsyncSerial{  

dispatch_queue_t queue = dispatch_queue_create("串行队列名", DISPATCH_QUEUE_SERIAL);       

dispatch_async(queue, ^{       

sleep(1);       

NSLog(@"在串行队列中执行第一个任务,线程:%@",[NSThread currentThread]);  

});   

dispatch_async(queue, ^{       

NSLog(@"在串行队列中执行第二个任务,线程:%@",[NSThread currentThread]);  

});   

dispatch_async(queue, ^{       

sleep(2);       

NSLog(@"在串行队列中执行第三个任务,线程:%@",[NSThread currentThread]);   

});   

dispatch_async(queue, ^{       

NSLog(@"在串行队列中执行第四个任务,线程:%@",[NSThread currentThread]);   

});}

/* 结果 2017-08-22 18:43:45.006 GCD简介与Demo[4481:267419] 在串行队列中执行第一个任务,线程:{number = 3, name = (null)}

2017-08-22 18:43:45.006 GCD简介与Demo[4481:267419] 在串行队列中执行第二个任务,线程:{number = 3, name = (null)}

2017-08-22 18:43:47.011 GCD简介与Demo[4481:267419] 在串行队列中执行第三个任务,线程:{number = 3, name = (null)}

2017-08-22 18:43:47.011 GCD简介与Demo[4481:267419] 在串行队列中执行第四个任务,线程:{number = 3, name = (null)}

结论:串行队列 + 异步执行,虽然各个任务都在在子线程中执行,但仍然是按照添加入队列的顺序,依次执行

*/

3.并行队列 + 同步执行

- (void)dispatchSyncConcurrent{   

dispatch_queue_t queue = dispatch_queue_create("并行队列名", DISPATCH_QUEUE_CONCURRENT);      

dispatch_sync(queue, ^{      

sleep(1);       

NSLog(@"在并行队列中执行第一个任务,线程:%@",[NSThread currentThread]); 

 });   

dispatch_sync(queue, ^{       

NSLog(@"在并行队列中执行第二个任务,线程:%@",[NSThread currentThread]);   

});   

dispatch_sync(queue, ^{       

sleep(2);       

NSLog(@"在并行队列中执行第三个任务,线程:%@",[NSThread currentThread]);  

});   

dispatch_sync(queue, ^{       

NSLog(@"在并行队列中执行第四个任务,线程:%@",[NSThread currentThread]);   

});}

/* 结果

2017-08-22 18:48:30.946 GCD简介与Demo[4535:272013] 在并行队列中执行第一个任务,线程:{number = 1, name = main}

2017-08-22 18:48:30.947 GCD简介与Demo[4535:272013] 在并行队列中执行第二个任务,线程:{number = 1, name = main}

2017-08-22 18:48:32.948 GCD简介与Demo[4535:272013] 在并行队列中执行第三个任务,线程:{number = 1, name = main}

2017-08-22 18:48:32.949 GCD简介与Demo[4535:272013] 在并行队列中执行第四个任务,线程:{number = 1, name = main}

结论:并行队列 + 同步执行,虽然队里是并行队列,但是由于是同步执行,所有任务都在主线程中执行,他们仍然是按照添加入队列的顺序,依次执行

*/

4.并行队列 + 同步执行

- (void)dispatchAsyncConcurrent{   

dispatch_queue_t queue = dispatch_queue_create("并行队列名", DISPATCH_QUEUE_CONCURRENT);       

dispatch_async(queue, ^{       

sleep(1);       

NSLog(@"在并行队列中执行第一个任务,线程:%@",[NSThread currentThread]);   

});  

dispatch_async(queue, ^{      

NSLog(@"在并行队列中执行第二个任务,线程:%@",[NSThread currentThread]);   

});   

dispatch_async(queue, ^{      

sleep(2);       

NSLog(@"在并行队列中执行第三个任务,线程:%@",[NSThread currentThread]);   

});  

dispatch_async(queue, ^{       

NSLog(@"在并行队列中执行第四个任务,线程:%@",[NSThread currentThread]);  

});}

/* 结果

2017-08-22 18:51:02.246 GCD简介与Demo[4574:274917] 在并行队列中执行第二个任务,线程:{number = 3, name = (null)}

2017-08-22 18:51:02.246 GCD简介与Demo[4574:274937] 在并行队列中执行第四个任务,线程:{number = 4, name = (null)}

2017-08-22 18:51:03.249 GCD简介与Demo[4574:274918] 在并行队列中执行第一个任务,线程:{number = 5, name = (null)}

2017-08-22 18:51:04.250 GCD简介与Demo[4574:274920] 在并行队列中执行第三个任务,线程:{number = 6, name = (null)}

结论:并行队列 + 异步执行,各个任务都在子线程中执行,并且同时开始执行

*/

5.GCD中的特殊队列

1.主队列:GCD自带的一个特殊的串行队列,使用dispatch_get_main_queue()获取

2.全局队列:GCD自带的一个特殊的并行队列,使用dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)获取

注意:DISPATCH_QUEUE_PRIORITY_DEFAULT是一个宏,它标记队列的优先级,有以下四种:

. DISPATCH_QUEUE_PRIORITY_HIGH 高优先级

. DISPATCH_QUEUE_PRIORITY_DEFAULT 默认优先级

. DISPATCH_QUEUE_PRIORITY_LOW 低优先级

. DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台优先级


三、线程间的通信以及单次执行方法

1.线程间的通信

一般用于在将网络请求、下载、上传等耗时操作放入子线程执行完毕后,回到主线程更新UI

注意:UI操作必须在主线程进行操作

- (void)gcdSendMessageFromAnotherThread{       

dispatch_async(dispatch_get_global_queue(0, 0), ^{      

NSLog(@"开始执行耗时操作,所处线程:%@",[NSThread currentThread]);      

sleep(3);       

NSLog(@"耗时操作执行完毕,开始进入主线程"); 

      dispatch_async(dispatch_get_main_queue(), ^{         

        NSLog(@"进入主线程,开始执行更新UI操作,所处线程:%@",[NSThread currentThread]);        });           

});}

/* 结果

2017-08-22 19:07:09.099 GCD简介与Demo[4698:290444] 开始执行耗时操作,所处线程:{number = 3, name = (null)}

2017-08-22 19:07:12.103 GCD简介与Demo[4698:290444] 耗时操作执行完毕,开始进入主线程

2017-08-22 19:07:12.103 GCD简介与Demo[4698:290380] 进入主线程,开始执行更新UI操作,所处线程:{number = 1, name = main}

*/

2.单次执行 dispatch_once  一般用于创建单例

- (void)dispatchOnce{   

static dispatch_once_t onceToken;  

dispatch_once(&onceToken, ^{       

NSLog(@"这里是只执行一次的代码,所处线程:%@",[NSThread currentThread]);   

});}

- (void)testDispatchOnce{   

dispatch_async(dispatch_get_global_queue(0, 0), ^{       

NSLog(@"第一次,所处线程:%@",[NSThread currentThread]);       

[self dispatchOnce];  

});   

dispatch_async(dispatch_get_global_queue(0, 0), ^{       

NSLog(@"第二次,所处线程:%@",[NSThread currentThread]);       

[self dispatchOnce];   

});   

dispatch_async(dispatch_get_global_queue(0, 0), ^{       

NSLog(@"第三次,所处线程:%@",[NSThread currentThread]);      

[self dispatchOnce];   

});  

dispatch_async(dispatch_get_global_queue(0, 0), ^{       

NSLog(@"第四次,所处线程:%@",[NSThread currentThread]);       

[self dispatchOnce];    }

);}

/*  结果

2017-08-22 19:12:15.813 GCD简介与Demo[4729:295603] 第三次,所处线程:{number = 5, name = (null)}

2017-08-22 19:12:15.813 GCD简介与Demo[4729:295617] 第四次,所处线程:{number = 6, name = (null)}

2017-08-22 19:12:15.813 GCD简介与Demo[4729:295600] 第二次,所处线程:{number = 4, name = (null)}

2017-08-22 19:12:15.813 GCD简介与Demo[4729:295601] 第一次,所处线程:{number = 3, name = (null)}

2017-08-22 19:12:15.814 GCD简介与Demo[4729:295603] 这里是只执行一次的代码,所处线程:{number = 5, name = (null)}

结论:dispatch_once封装的代码只在第一次执行了一次

*/


四、GCD的其它处理函数

1.栅栏方法dispatch_barrier

这个函数相当于一个停顿,在这个函数之前加入队列的任务先执行,执行完毕后执行栅栏函数中的任务,然后再执行栅栏函数之后的任务

注意:dispatch_barrier_async和dispatch_barrier_sync的区别:仅仅是执行的线程不同,dispatch_barrier_sync会同步执行(在主线程中执行),dispatch_barrier_async在子线程中执行

- (void)dispatchBarrier{   

dispatch_queue_t queue = dispatch_queue_create("并行队列名", DISPATCH_QUEUE_CONCURRENT);   

dispatch_async(queue, ^{       

NSLog(@"第一个任务,线程:%@",[NSThread currentThread]);   

});  

dispatch_async(queue, ^{       

NSLog(@"第二个任务,线程:%@",[NSThread currentThread]);   

});   

dispatch_barrier_sync(queue, ^{      

sleep(1);       

NSLog(@"执行barrier同步任务,线程:%@",[NSThread currentThread]);       

sleep(1);   

});   

dispatch_barrier_async(queue, ^{       

sleep(1);       

NSLog(@"执行barrier异步任务,线程:%@",[NSThread currentThread]);       

sleep(1);   

});  

dispatch_async(queue, ^{       

NSLog(@"第三个任务,线程:%@",[NSThread currentThread]);  

});   

dispatch_async(queue, ^{       

NSLog(@"第四个任务,线程:%@",[NSThread currentThread]);   

});}

/* 结果:

2017-08-22 19:35:16.874 GCD简介与Demo[4949:314086] 第一个任务,线程:{number = 3, name = (null)}

2017-08-22 19:35:16.874 GCD简介与Demo[4949:314083] 第二个任务,线程:{number = 4, name = (null)}

2017-08-22 19:35:17.876 GCD简介与Demo[4949:313797] 执行barrier同步任务,线程:{number = 1, name = main}

2017-08-22 19:35:19.882 GCD简介与Demo[4949:314083] 执行barrier异步任务,线程:{number = 4, name = (null)}

2017-08-22 19:35:20.887 GCD简介与Demo[4949:314083] 第三个任务,线程:{number = 4, name = (null)}

2017-08-22 19:35:20.887 GCD简介与Demo[4949:314084] 第四个任务,线程:{number = 5, name = (null)}

*/

2.GCD延迟执行的方法dispatch_after

- (void)dispatchAfter{   

dispatch_async(dispatch_get_global_queue(0, 0), ^{       

NSLog(@"执行子线程任务,线程:%@",[NSThread currentThread]);        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{           

NSLog(@"延时两秒后执行主线程任务,线程:%@",[NSThread currentThread]);       

});   

});    }

/* 结果

2017-08-22 19:40:10.880 GCD简介与Demo[4982:319700] 执行子线程任务,线程:{number = 3, name = (null)}

2017-08-22 19:40:13.072 GCD简介与Demo[4982:319625] 延时两秒后执行主线程任务,线程:{number = 1, name = main}

*/

3.GCD的快速迭代方法dispatch_apply

dispatch_apply类似for循环,它是一个同步调用,任务将会执行给定次数,如果是并行队里中,那么会并发执行任务,如果是串行队列中,则会顺序执行其中的代码

应用场景:

1.防止创建过多线程导致线程爆炸

2.多个任务并行执行,比如字典数组的快速解析

- (void)dispatchApplyAsync{   

NSArray *array = @[@1,@2,@3,@4,@5];   

dispatch_async(dispatch_get_global_queue(0, 0), ^{     

dispatch_apply(array.count, dispatch_get_global_queue(0, 0), ^(size_t index) {      

  NSLog(@"%@开始执行 %zu times",[NSThread currentThread],index+1);         

sleep((unsigned int)(index+1));     

});       

dispatch_async(dispatch_get_main_queue(), ^{          

NSLog(@"回到主线程操作");      

});   

});}

/* 结果

2017-08-22 19:54:38.939 GCD简介与Demo[5088:331292]{number = 5, name = (null)}开始执行 3 times

2017-08-22 19:54:38.939 GCD简介与Demo[5088:331304]{number = 4, name = (null)}开始执行 2 times

2017-08-22 19:54:38.939 GCD简介与Demo[5088:331290]{number = 3, name = (null)}开始执行 1 times

2017-08-22 19:54:38.939 GCD简介与Demo[5088:331289]{number = 6, name = (null)}开始执行 4 times

2017-08-22 19:54:38.940 GCD简介与Demo[5088:331290]{number = 3, name = (null)}开始执行 5 times

2017-08-22 19:54:42.943 GCD简介与Demo[5088:331197] 回到主线程操作

可以看出,dispatch_apply之后的操作将会在dispatch_apply全部执行完毕后才开始执行

*/


- (void)dispatchApplySync{   

NSArray *array = @[@1,@2,@3,@4,@5];   

dispatch_queue_t queue = dispatch_queue_create("串行队列名", DISPATCH_QUEUE_SERIAL);   

dispatch_async(dispatch_get_global_queue(0, 0), ^{      

dispatch_apply(array.count, queue, ^(size_t index) {           

NSLog(@"%@开始执行 %zu times",[NSThread currentThread],index+1);    

  sleep((unsigned int)(index+1));       

});       

dispatch_async(dispatch_get_main_queue(), ^{           

NSLog(@"回到主线程操作");       

});   

});}

/*

结果

2017-08-22 19:58:03.952 GCD简介与Demo[5110:334355]{number = 3, name = (null)}开始执行 1 times

2017-08-22 19:58:04.954 GCD简介与Demo[5110:334355]{number = 3, name = (null)}开始执行 2 times

2017-08-22 19:58:06.958 GCD简介与Demo[5110:334355]{number = 3, name = (null)}开始执行 3 times

2017-08-22 19:58:09.963 GCD简介与Demo[5110:334355]{number = 3, name = (null)}开始执行 4 times

2017-08-22 19:58:13.969 GCD简介与Demo[5110:334355]{number = 3, name = (null)}开始执行 5 times

2017-08-22 19:58:18.972 GCD简介与Demo[5110:334292] 回到主线程操作

可以看出,串行队列中,所有的任务按照顺序依次执行,执行完毕后,再执行后面的代码

*/


4.GCD队列组dispatch_group_t

应用场景:多个任务并发执行,我们需要获取到全部执行完毕后的时间节点(使用dispatch_group_notify获取)

- (void)dispatchGroup{

dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_queue_create("并行队列名", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_async(group, queue, ^{

NSLog(@"执行完第一个任务");

});

dispatch_group_async(group, queue, ^{

sleep(1);

NSLog(@"执行完第二个任务");

});

dispatch_group_async(group, queue, ^{

sleep(2);

NSLog(@"执行完第三个任务");

});

dispatch_group_notify(group, queue, ^{

NSLog(@"任务全部执行完毕");

});

}

/*

2017-08-22 20:06:04.304 GCD简介与Demo[5155:341594] 执行完第一个任务

2017-08-22 20:06:05.306 GCD简介与Demo[5155:341596] 执行完第二个任务

2017-08-22 20:06:06.306 GCD简介与Demo[5155:341593] 执行完第三个任务

2017-08-22 20:06:06.306 GCD简介与Demo[5155:341593] 任务全部执行完毕

*/


五、GCD使用信号量控制并发dispatch_semaphore

1.函数讲解:

.dispatch_semaphore_create

函数原型:dispatch_samaphore_t dispatch_semaphore_create(long value)

传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。 值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。

.dispatch_semaphore_signal

函数原型:long dispatch_semaphore_signal(dispatch_semaphore_tdsema)

这个函数会使传入的信号量dsema的值加1

当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。

.dispatch_semaphore_wait

函数原型: long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout)

这个函数会使传入的信号量dsema的值减1

如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t)

不能直接传入整形或float型数,如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。

如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。

dispatch_semaphore_wait的返回值也为long型。当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒。当其返回不为0时,表示timeout发生。

2.timeout参数

.DISPATCH_TIME_NOW 表示当前(立刻超时)

.DISPATCH_TIME_FOREVER  表示遥远的未来(永不超时)

具体可参照:http://www.jianshu.com/p/ed312c734369

- (void)dispatchSemaphore{

//创建信号,设置最大并发数为2

dispatch_semaphore_t sem = dispatch_semaphore_create(2);

for (int i = 0; i < 10; i++) {

dispatch_async(dispatch_get_global_queue(0, 0), ^{

long a = dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

NSLog(@"第%d个任务执行,此时a为:%ld",i+1,a);

sleep(1);

long b = dispatch_semaphore_signal(sem);

NSLog(@"b为:%ld",b);

});

}

}

/*

结果

2017-08-22 21:11:48.928 GCD简介与Demo[716:12343] 第2个任务执行

2017-08-22 21:11:48.928 GCD简介与Demo[716:12330] 第1个任务执行

2017-08-22 21:11:49.931 GCD简介与Demo[716:12329] 第4个任务执行

2017-08-22 21:11:49.931 GCD简介与Demo[716:12332] 第3个任务执行

2017-08-22 21:11:50.933 GCD简介与Demo[716:12345] 第5个任务执行

2017-08-22 21:11:50.933 GCD简介与Demo[716:12346] 第6个任务执行

2017-08-22 21:11:51.937 GCD简介与Demo[716:12347] 第7个任务执行

2017-08-22 21:11:51.937 GCD简介与Demo[716:12348] 第8个任务执行

2017-08-22 21:11:52.940 GCD简介与Demo[716:12350] 第10个任务执行

2017-08-22 21:11:52.940 GCD简介与Demo[716:12349] 第9个任务执行

可以看出,同时执行任务的只有两个,这就控制了最大并发数

*/


六、GCD定时器 dispatch_source_set_timer

NSTimer是我们最为熟悉的定时器,但是NSTimer有很大的缺点,并不准确。而GCD定时器,则是严格按照规定好的规格去做事,它更为准确

NSTimer是在RunLoop的基础上执行的,而RunLoop是在GCD的基础上实现的,所以GCD可以算是更为高级

@interface ViewController ()

//这里不需要*号,因为dispatch_source_t实现中已经加进了指针*号

@property (nonatomic,strong) dispatch_source_t timer;

@property (nonatomic,strong) UIView *animationView;

@end

@implementation ViewController

- (void)viewDidLoad {

[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.

_animationView = [[UIView alloc]initWithFrame:CGRectMake(0, self.view.bounds.size.height/2.-50, 100, 100)];

_animationView.backgroundColor = [UIColor redColor];

[self.view addSubview:_animationView];

[self beginGCDTimer];

}


- (void)beginGCDTimer{

//如果定时器已经开启,则将它关闭,并置空

if (self.timer) {

dispatch_source_cancel(self.timer);

self.timer = nil;

}

//创建队列

dispatch_queue_t queue = dispatch_queue_create("并行队列名", DISPATCH_QUEUE_CONCURRENT);

//创建定时器

self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

//设置开始时间 后面的参数意思为从现在开始后2秒开始执行定时器回调

dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC);

//设置时间间隔 uint64_t为unsigned long long的别名,无符号长长整形

uint64_t interval = 0.01 * NSEC_PER_SEC;

//设置定时器 最后一个参数为偏差,一般设置为0

dispatch_source_set_timer(_timer, start, interval, 0);

//设置回调

dispatch_source_set_event_handler(_timer, ^{

CGFloat x = self.animationView.frame.origin.x;

CGFloat width = self.view.bounds.size.width;

x += 1;

if (x >= width) {

x = 0;

}

CGRect frame = self.animationView.frame;

frame.origin.x = x;

dispatch_async(dispatch_get_main_queue(), ^{

self.animationView.frame = frame;

});

});

//定时器默认是暂停的,需要手动启动

dispatch_resume(_timer);

}

备注:

关于时间的一些宏

#define NSEC_PER_SEC 1000000000ull

#define USEC_PER_SEC 1000000ull

#define NSEC_PER_USEC 1000ull

NSEC:纳秒。

USEC:微秒。

SEC:秒

PER:每

1    NSEC_PER_SEC,每秒有多少纳秒。

2    USEC_PER_SEC,每秒有多少毫秒。(注意是指在纳秒的基础上)

3    NSEC_PER_USEC,每毫秒有多少纳秒。

Demo地址 

你可能感兴趣的:(GCD基本用法大全)