iOS开发之GCD使用
一、队列
1、串行队列(Serial Dispatch Queue)
// 串行队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("标识符
", DISPATCH_QUEUE_SERIAL);
2、并发队列(Concurrent Dispatch Queue)
// 并发队列的创建方法
dispatch_queue_t queue = dispatch_queue_create("标识符", DISPATCH_QUEUE_CONCURRENT);
3、系统主队列(Main Dispatch Queue)
// 主队列的获取方法 dispatch_queue_t queue = dispatch_get_main_queue();//相当于同步串行队列
dispatch_queue_t queue = dispatch_queue_create("标识符", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ });
4、全局并发队列(Global Dispatch Queue)
// 全局并发队列的获取方法 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)//相当于异步并行队列
dispatch_queue_t queue = dispatch_queue_create("标识符",DISPATCH_QUEUE_CONCURRENT
); dispatch_async(queue, ^{ });
二、任务
GCD 提供了同步执行任务的创建方法 dispatch_sync
和异步执行任务创建方法 dispatch_async
。
// 同步执行任务创建方法 dispatch_sync(queue, ^{ // 这里放同步执行任务代码 }); // 异步执行任务创建方法 dispatch_async(queue, ^{ // 这里放异步执行任务代码 });
队列和任务可以随意组合,达到不同目的。注意: 『主线程』 中调用 『主队列』+『同步执行』 会导致死锁问题。这是因为 主队列中追加的同步任务 和 主线程本身的任务 两者之间相互等待,阻塞了 『主队列』,最终造成了主队列所在的线程(主线程)死锁问题。
三、GCD 线程间的通信
//线程间通信 (开启子线程处理完耗时操作,回到主线程刷新UI) - (void)communication { // 获取全局并发队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获取主队列 dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_async(queue, ^{ // 异步追加任务 1 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 // 回到主线程 dispatch_async(mainQueue, ^{ // 追加在主线程中执行的任务 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 }); }); }
四、GCD 栅栏方法 (dispatch_barrier_async/dispatch_barrier_sync)
dispatch_barrier_async就是将异步执行分成两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。
- (void)gcdBarrier { // 创建队列 dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{ NSLog(@"----1-----%@", [NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"----2-----%@", [NSThread currentThread]); }); dispatch_barrier_async(queue, ^{ NSLog(@"----barrier-----%@", [NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"----3-----%@", [NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"----4-----%@", [NSThread currentThread]); }); // 可以看出在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行栅栏后边的操作 }//珊栏方法的
注意:1. dispatch_barrier_async , 会将添加到queue前面的任务执行完之后,才会执行后面的任务,并不会阻塞当前的线程
;
2.dispatch_barrier_sync , 会将添加到queue前面的任务执行完之后,才会执行后面的任务,并且会阻塞当前的线程
五、GCD 延时执行方法:dispatch_after
// 延时执行方法 dispatch_after - (void)after { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 NSLog(@"asyncMain---begin"); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 2.0 秒后异步追加任务代码到主队列,并开始执行 NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程 }); }
六、GCD 一次性代码(只执行一次):dispatch_once
dispatch_once
能保证任务只会被执行一次,即使同时多线程调用也是线程安全的。常用于创建单例、swizzeld method等功能。
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //创建单例、method swizzled或其他任务 });
七、GCD 快速迭代方法:dispatch_apply
void dispatch_apply(size_t iterations, dispatch_queue_t queue,
DISPATCH_NOESCAPE void (^block)(size_t));
该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束,好处是可以重复执行某项操作并复用我们的Block了!
第一个参数为重复次数;
第二个参数为追加对象的Dispatch Queue;
第三个参数为追加的操作,追加的Block中带有参数,这是为了按第一个参数重复追加Block并区分各个Block而使用。
dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。这是因为该函数会等待这些操作执行完毕再返回,内部操作执行是否同步依赖于传入的queue,外部就必定是同步的。
- (void)gcdDispatchApply { //获取一个全局队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //dispatch_queue_t queue = dispatch_queue_create("com.gcd.dispatchApply.serialQueue", NULL); //在全局队列queue上执行十次输出操作 dispatch_apply(10, queue, ^(size_t index) { NSLog(@"%zu", index); }); NSLog(@"done!"); }
注意:当传入的queue是并行队列时,重复执行的操作输出是无序的,done输出始终在最后。
当传入的queue是串行队列时,重复执行的操作输出是有序的,done输出始终在最后。
八、GCD 队列组:dispatch_group
有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个耗时任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。
- 调用队列组的
dispatch_group_async
先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的dispatch_group_enter
、dispatch_group_leave
组合来实现dispatch_group_async
。 - 调用队列组的
dispatch_group_notify
回到指定线程执行任务。或者使用dispatch_group_wait
回到当前线程继续向下执行(会阻塞当前线程)。
1、dispatch_group_notify
监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到 group 中,并执行任务。
// 队列组 dispatch_group_notify - (void)groupNotify { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 NSLog(@"group---begin"); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 追加任务 1 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 追加任务 2 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边任务 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程 NSLog(@"group---end"); }); }
//当前面两个任务都执行完成之后,才执行dispatch_group_notify
相关 block 中的任务。
2、dispatch_group_wait
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
// 队列组 dispatch_group_wait - (void)groupWait { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 NSLog(@"group---begin"); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 追加任务 1 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 追加任务 2 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 }); // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程) dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"group---end"); }
//当前面两个任务执行完成之后,才执行dispatch_group_wait
之后的操作。但是,使用dispatch_group_wait
会阻塞当前线程。
3、dispatch_group_enter、dispatch_group_leave
dispatch_group_enter
标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1
dispatch_group_leave
标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1。
当 group 中未执行完毕任务数为0的时候,才会使 dispatch_group_wait
解除阻塞,以及执行追加到 dispatch_group_notify
中的任务。
/** * 队列组 dispatch_group_enter、dispatch_group_leave */ - (void)groupEnterAndLeave { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 NSLog(@"group---begin"); dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_group_enter(group); dispatch_async(queue, ^{ // 追加任务 1 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(queue, ^{ // 追加任务 2 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 dispatch_group_leave(group); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 等前面的异步操作都执行完毕后,回到主线程. [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程 NSLog(@"group---end"); }); }
//当所有任务执行完成之后,才执行dispatch_group_notify
中的任务。这里的dispatch_group_enter
、dispatch_group_leave
组合,其实等同于dispatch_group_async。
九、GCD 信号量:dispatch_semaphore
GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,计数减 1 且不等待,可通过。
Dispatch Semaphore 提供了三个方法:
1.dispatch_semaphore_create
:创建一个 Semaphore 并初始化信号的总量
2.dispatch_semaphore_signal
:发送一个信号,让信号总量加 1
3.dispatch_semaphore_wait
:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
Dispatch Semaphore 在实际开发中主要用于:
1.保持线程同步,将异步执行任务转换为同步执行任务
2.保证线程安全,为线程加锁
1、Dispatch Semaphore 线程同步
/** * semaphore 线程同步 */ - (void)semaphoreSync { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 NSLog(@"semaphore---begin"); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);//1.创建一个 Semaphore 并初始化信号的总量 __block int number = 0; dispatch_async(queue, ^{ // 追加任务 1 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 number = 100; dispatch_semaphore_signal(semaphore);//3.发送一个信号,让信号总量加 1 }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//2.可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。 NSLog(@"semaphore---end,number = %zd",number); }
//这是一个异步线程,没加信号量的话,应该是number = 0;加上信号量 number = 100;
1.semaphore 初始创建时计数为 0。
2.异步执行
将 任务 1
追加到队列之后,不做等待,接着执行 dispatch_semaphore_wait
方法,semaphore 减 1,此时 semaphore == -1
,当前线程进入等待状态。
3.然后,异步任务 1 开始执行。任务 1 执行到 dispatch_semaphore_signal
之后,总信号量加 1,此时 semaphore == 0
,正在被阻塞的线程(主线程)恢复继续执行。
4.最后打印 semaphore---end,number = 100
。
2、 线程安全(使用 semaphore 加锁)
/** * 线程安全:使用 semaphore 加锁 * 初始化火车票数量、卖票窗口(线程安全)、并开始卖票 */ - (void)initTicketStatusSave { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程 NSLog(@"semaphore---begin"); semaphoreLock = dispatch_semaphore_create(1); self.ticketSurplusCount = 50; // queue1 代表北京火车票售卖窗口 dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL); // queue2 代表上海火车票售卖窗口 dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL); __weak typeof(self) weakSelf = self; dispatch_async(queue1, ^{ [weakSelf saleTicketSafe]; }); dispatch_async(queue2, ^{ [weakSelf saleTicketSafe]; }); } /** * 售卖火车票(线程安全) */ - (void)saleTicketSafe { while (1) { // 相当于加锁 dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER); if (self.ticketSurplusCount > 0) { // 如果还有票,继续售卖 self.ticketSurplusCount--; NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]); [NSThread sleepForTimeInterval:0.2]; } else { // 如果已卖完,关闭售票窗口 NSLog(@"所有火车票均已售完"); // 相当于解锁 dispatch_semaphore_signal(semaphoreLock); break; } // 相当于解锁 dispatch_semaphore_signal(semaphoreLock); } }
//在考虑了线程安全的情况下,使用dispatch_semaphore
机制之后,得到的票数是正确的,没有出现混乱的情况。我们也就解决了多个线程同步的问题。
十、GCD 定时器:dispatch_source
用UIButton的类别实现一个注册倒计时的功能
- (void)countDownFromTime:(NSInteger)startTime title:(NSString *)title unitTitle:(NSString *)unitTitle mainColor:(UIColor *)mColor countColor:(UIColor *)color { __weak typeof(self) weakSelf = self; // 剩余的时间 __block NSInteger remainTime = startTime; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); // 每秒执行一次 dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC); // 子线程(queue)执行event_handler dispatch_source_set_event_handler(timer, ^{ if (remainTime <= 0) { // 倒计时结束 dispatch_source_cancel(timer); // 主线程更新UI dispatch_async(dispatch_get_main_queue(), ^{ weakSelf.backgroundColor = mColor; [weakSelf setTitle:title forState:UIControlStateNormal]; weakSelf.enabled = YES; }); } else { NSString *timeStr = [NSString stringWithFormat:@"%ld", remainTime]; dispatch_async(dispatch_get_main_queue(), ^{ weakSelf.backgroundColor = color; [weakSelf setTitle:[NSString stringWithFormat:@"%@%@",timeStr,unitTitle] forState:UIControlStateDisabled]; weakSelf.enabled = NO; }); remainTime--; } }); dispatch_resume(timer); }