iOS GCD常规使用

前言:本文的主要内容是:

  1. iOS中常用的几种多线程技术比较
  2. GCD知识铺垫
  3. GCD的使用
  4. GCD中一些系统提供的其他常用的dispatch方法

1. iOS中常用的几种多线程技术比较

pthread:通用于Unix/Linux/Windows的C语言线程管理API,可移植性强, 但是使用繁琐,需要使用者管理线程生命周期。
NSThread:使用Objective-C实现,轻量级的线程管理,但也需要手动管理线程的生命周期
NSOperation:基于GCD,使用Objective-C实现的面向对象的线程管理,比GCD更高级,但是处理简单任务会比GCD代码更多。
GCD : 旨在优化多核环境中的并发操作,简单易用,效率高,速度快,能自动管理线程生命周期(创建线程、调度任务、销毁线程)。
线程的生命周期图:

线程的生命周期.png

2. GCD知识铺垫

** 竞争&同步:两个线程抢夺同一个资源,就会竞争,为了防止竞争,一个线程拥有资源的时候,会对资源加锁,另一个线程就要等待解锁以后再拥有这个资源,这叫同步。
** 死锁:
两个线程互相等待对方释放资源
主线程&后台线程:主线程也叫前台线程,程序启动的默认线程,操作UI的线程。后台线程,即非主线程,用于不影响主线程的完成一些任务
并行队列&串行队列:并行,就是队列中几个任务一起完成。串行,就是队列几个任务一个接着一个完成。
同步线程&异步线程:同步执行线程,等待新线程执行完以后,再继续执行当前线程,很少用到。异步执行线程,在执行新线程的同时,继续执行当前线程,常用。
并发VS并行:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔内发生。并行是基于多核设备的,并发一般基于CPU的分时操作。

3. GCD的使用

GCD的使用步骤

  • 创建线程队列:主队列,全局并行队列,手动创建串行队列
  • 选择执行方式:同步(较少使用),异步,延时
  • 添加需要执行的任务:也就是想要创建队列做什么,以Block块语句形式创建
  • 任务被执行:Block按照设计被执行

创建线程队列

主队列:
dispatch_queue_t queue = dispatch_get_main_queue();
全局并行队列:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
手动创建串行队列:
dispatch_queue_t queue = dispatch_queue_create("com.realank.GCDDemo.myQueue", NULL);

选择线程执行方式

异步:
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
同步
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
延时:
void dispatch_after(dispatch_time_t when,dispatch_queue_t queue, dispatch_block_t block);

GCD术语解析

术语解析.png

同步异步在不同线程队列的执行情况.png

异步(async)执行线程
1、 异步后台线程主要用途:执行较慢的任务,例如大量计算,网络请求等

//异步后台并发线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"在异步并行队列中执行");
    });

//自创建的异步后台串行线程。
dispatch_queue_t queue dispatch_queue_create("com.realank.GCDDemo.myQueue", NULL);
dispatch_async(queue, ^{
        NSLog(@"在异步串行队列中执行");
    });

2、异步主线程

//  主要用途:用于在后台线程的任务将要完成时,切换到主线程更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"在异步主线程中执行");
    });

同步(sync)执行线程
1、同步后台线程
主要用途:在新线程中执行任务,并且等待线程执行完毕再向后执行,几乎不用

//同步后台并行线程
dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     NSLog(@"在同步并行队列中执行");
 });
// 同步后台串行线
 dispatch_queue_t queue = dispatch_queue_create("com.realank.GCDDemo.myQueue", NULL);
 dispatch_sync(queue, ^{
     NSLog(@"在同步串行队列中执行");
 });

2、同步主线程(慎用)
主要用途:只有在其它线程中才可能执行此方法,否则会死锁

dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@“在同步主线程中执行,慎用,否则会死锁”);
});

注意 不要嵌套使用同步执行的串行队列任务(会产生死锁,打破嵌套、同步、串行其中一个条件就可以),如嵌套使用同步执行的并发队列任务(不会产生死锁,程序正常运行)。

注意循环强引用

不要在Block中使用self.png
__weak __typeof(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        weakSelf.string = @“hello";
    });

GCD中一些系统提供的常用dispatch方法

  • dispatch_after延时添加到队列
    主要用途:dispatch_after只是延时提交block,并不是延时后立即执行,并不能做到精确控制。
dispatch_time_t delayTime3 = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
dispatch_time_t delayTime2 = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
NSLog(@"current task");
dispatch_after(delayTime3, mainQueue, ^{
  NSLog(@"3秒之后添加到队列");
});
dispatch_after(delayTime2, mainQueue, ^{
   NSLog(@"2秒之后添加到队列");
});
NSLog(@"next task");

控制台输出如下:

2015-11-19 15:50:19.369 Whisper[2725:172593] current task
2015-11-19 15:50:19.370 Whisper[2725:172593] next task
2015-11-19 15:50:21.369 Whisper[2725:172593] 2秒之后添加到队列
2015-11-19 15:50:22.654 Whisper[2725:172593] 3秒之后添加到队列
  • Group queue (队列组)

使用场景: 同时下载多个图片,所有图片下载完成之后去更新UI(需要回到主线程)。
原理:使用函数dispatch_group_create创建dispatch group,然后使用函数dispatch_group_async来将要执行的block任务提交到一个dispatch queue。同时将他们添加到一个组,等要执行的block任务全部执行完成之后,通过dispatch_group_notify,可以直接监听组里所有线程完成情况。

    1   dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    2   dispatch_queue_t mainQueue = dispatch_get_main_queue();
    3   dispatch_group_t groupQueue = dispatch_group_create();
    4   NSLog(@"current task");
    5   dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
    6      NSLog(@"并行任务1");
    7   });
    8   dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
    9      NSLog(@"并行任务2");
    10  });
    11  dispatch_group_notify(groupQueue, mainQueue, ^{
    12     NSLog(@"groupQueue中的任务 都执行完成,回到主线程更新UI");
    13  });
    14  NSLog(@"next task");

控制台输出:

2015-11-19 13:47:55.117 Whisper[1645:97116] current task
2015-11-19 13:47:55.117 Whisper[1645:97116] next task
2015-11-19 13:47:55.119 Whisper[1645:97178] 并行任务1
2015-11-19 13:47:55.119 Whisper[1645:97227] 并行任务2
2015-11-19 13:47:55.171 Whisper[1645:97116] groupQueue中的任务 都执行完成,回到主线程更新UI
  • 阻塞当前线程dispatch_group_wait
    dispatch_group_wait ,它会阻塞当前线程,直到组里面所有的任务都完成或者等到某个超时发生。
    1   dispatch_group_t groupQueue = dispatch_group_create();
    2   dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
    3   dispatch_queue_t conCurrentGlobalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    4   NSLog(@"current task");
    5   dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
    6   
    7      long isExecuteOver = dispatch_group_wait(groupQueue, delayTime);
    8      if (isExecuteOver) {
    9          NSLog(@"wait over");
    10     } else {
    11         NSLog(@"not over");
    12     }
    13     NSLog(@"并行任务1");
    14  });
    15  dispatch_group_async(groupQueue, conCurrentGlobalQueue, ^{
    16     NSLog(@"并行任务2");
    17  });

控制台输出如下:

2015-11-19 14:37:29.514 Whisper[2426:126683] current task
2015-11-19 14:37:29.518 Whisper[2426:126791] 并行任务2
2015-11-19 14:37:39.515 Whisper[2426:126733] wait over
2015-11-19 14:37:39.516 Whisper[2426:126733] 并行任务1
  • dispatch_apply多次执行
    dispatch_apply函数:dispatch_apply是同步执行的函数,不会立刻返回,在执行完block中的任务后才会返回。功能是把一项任务提交到队列中多次执行,队列可以是串行也可以是并行。
    为了不阻塞主线程,dispatch_apply正确使用方法是把dispatch_apply放在异步队列中调用,然后执行完成后通知主线程
使用示例:
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
NSLog(@"current task");
dispatch_async(globalQueue, ^{
dispatch_queue_t applyQueue = dispatch_get_global_queue(0, 0);
//第一个参数,3--block执行的次数
//第二个参数,applyQueue--block任务提交到的队列
//第三个参数,block--需要重复执行的任务
dispatch_apply(3, applyQueue, ^(size_t index) {
      NSLog(@"current index %@",@(index));
      sleep(1);
});
NSLog(@"dispatch_apply 执行完成");
dispatch_queue_t mainQueue = dispatch_get_main_queue();
dispatch_async(mainQueue, ^{
      NSLog(@"回到主线程更新UI");
});
});
NSLog(@"next task");
```
控制台输出如下:
```
2015-11-19 16:24:45.015 Whisper[4034:202269] current task
2015-11-19 16:24:45.016 Whisper[4034:202269] next task
2015-11-19 16:24:45.016 Whisper[4034:202347] current index 0
2015-11-19 16:24:45.016 Whisper[4034:202344] current index 1
2015-11-19 16:24:45.016 Whisper[4034:202345] current index 2
2015-11-19 16:24:46.021 Whisper[4034:202347] dispatch_apply 执行完成
2015-11-19 16:24:46.021 Whisper[4034:202269] 回到主线程更新UI
嵌套使用dispatch_apply会导致死锁。
```
* dispatch_once 执行一次
 dispatch_once保证在app运行期间,block中的代码只执行一次
    ◦   经典使用场景---单例
    ◦   单例对象ShareManager的定义:

```
    2    ShareManager的.h文件
    3   #import <Foundation/Foundation.h>
    4   @interface ShareManager : NSObject
    5   @property (nonatomic, copy) NSString *someProperty;
    6   + (ShareManager *)shareManager;
    7   + (ShareManager *)sharedManager;
    8   @end
    9   
    10  ShareManager的.m文件
    11  #import "ShareManager.h"
    12  @implementation ShareManager
    13  static ShareManager *sharedManager = nil;
    14  //GCD实现单例功能
    15  + (ShareManager *)shareManager
    16  {
    17   static dispatch_once_t onceToken;
    18   dispatch_once(&onceToken, ^{
    19       sharedManager = [[self alloc] init];
    20   });
    21   return sharedManager;
    22  }
    23  //在ARC下,非GCD,实现单例功能
    24  + (ShareManager *)sharedManager
    25  {
    26   @synchronized(self) {
    27       if (!sharedManager) {
    28           sharedManager = [[self alloc] init];
    29       }
    30   }
    31   return sharedManager;
    32  }
    33  - (instancetype)init{
    34   self = [super init];
    35   if (self) {
    36        _someProperty =@"Default Property Value";
    37   }
    38   return self;
    39  }
    40  @end
    41  
    42  ShareManager的使用
    43  #import "ShareManager.h"
    44  在需要使用的函数中,直接调用下面的方法
    45  ShareManager *share = [ShareManager sharedManager];
    46  NSLog(@"share is %@",share.someProperty);

```
* dispatch_barrier_async栅栏的作用
功能:是在并行队列中,等待在dispatch_barrier_async之前加入的队列全部执行完成之后(这些任务是并发执行的)再执行dispatch_barrier_async中的任务,dispatch_barrier_async中的任务执行完成之后,再去执行在dispatch_barrier_async之后加入到队列中的任务(这些任务是并发执行的)。
图解:
![dispatch_barrier_async栅栏的作用.jpg](http://upload-images.jianshu.io/upload_images/2050812-d0782d71b810774b.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
使用示例:
```
dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.dullgrass.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(conCurrentQueue, ^{
  NSLog(@"dispatch 1");
});
dispatch_async(conCurrentQueue, ^{
   NSLog(@"dispatch 2");
});
dispatch_barrier_async(conCurrentQueue, ^{
   NSLog(@"dispatch barrier");
});
dispatch_async(conCurrentQueue, ^{
   NSLog(@"dispatch 3");
});
dispatch_async(conCurrentQueue, ^{
   NSLog(@"dispatch 4");
});
```
控制台输出:
```
2015-11-19 18:12:34.125 Whisper[22633:297257] dispatch 1
2015-11-19 18:12:34.125 Whisper[22633:297258] dispatch 2
2015-11-19 18:12:34.126 Whisper[22633:297258] dispatch barrier
2015-11-19 18:12:34.127 Whisper[22633:297258] dispatch 3
2015-11-19 18:12:34.127 Whisper[22633:297257] dispatch 4

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

推荐阅读更多精彩内容