更正:队列名称的作用的图中,箭头标注的有些问题,已修正
本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末
如果觉得本文内容过长,请前往本人 “简书
 
1.0 GCD简介
 
GCD概念 :(Grand Central Dispatch)可译为“伟大的中枢调度器”
  • 纯C语言,提供了非常多强大的函数
GCD 的优势: 
  • GCD是苹果公司为多核的并行运算提出的解决方案
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
GCD的两个核心概念: 
1) 任务:执行什么操作
2) 队列:用来存放任务
 
注意:
(1)GCD存在于 libdispatch 这个库中,这个调度库包含了GCD的所有的东西,但任何IOS程序,默认就加载了这个库,在程序运行的过程中会动态的加载这个库,不需要我们手动导入。
(2)GCD是纯C语言的,因此我们在编写GCD相关代码的时候,面对的函数,而不是方法。
(3)GCD中的函数大多数都以dispatch开头。
 
2.0 GCD的使用

GCD的使用步骤:
步骤1 : 创建队列
步骤2 : 创建任务
  • 确定想做的事情
步骤3:把任务添加到队列
  • GCD会自动将队列中的任务取出,放到对应的线程中执行
  • 任务的取出遵循队列的FIFO原则:先进先出,后进后出
 
2.1 GCD的使用 - 任务

任务的执行:
有两个函数来执行任务:
  • 同步
  • 异步
说明:把右边的参数(任务)dispatch_block_t block 提交给左边的参数(队列)dispatch_queue_t queue 进行执行。
 ()任务的执行:同步

 //queue:队列    block:任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); ()任务的执行:异步 - 函数1 //queue:队列 block:任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block); - 函数2 // 在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

 
【区别】同步 & 异步:同步和异步决定了要不要开启新的线程
  • 同步:只能在当前线程中执行任务,不具备开启新线程的能力
  • 异步:可以在新的线程中执行任务,具备开启新线程的能力
【区别】并发 & 串行:并发和串行决定了任务的执行方式
  • 并发:允许多个任务并发(同时)执行
  • 串行:一个任务执行完毕后,再执行下一个任务
 
 2.2 GCD的使用 - 队列

队列分类:
(1)并发队列(Concurrent Dispatch Queue)
  • 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
  • 并发功能只有在异步(dispatch_async)函数下才有效
(2)串行队列(Serial Dispatch Queue)
  • 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
 
release:
  • 凡是函数名种带有create\copy\new\retain等字眼, 都应该在不需要使用这个数据的时候进行release
  • GCD的数据类型在ARC环境下不需要再做release         dispatch_release(queue); // 非ARC需要释放手动创建的队列
  • CF(Core Foundation)的数据类型在ARC环境下还是需要再做release
 
队列名称的作用:将来调试的时候,可以看得出任务是在哪个队列中执行的。
 
2.2.1 创建队列 - 并发队列

并行队列中的任务是多个任务同时执行的  :
(1)如果异步任务前面有同步任务 就会先执行同步任务同步任务是按顺序执行的任务等他执行完了才会执行并行中的异步任务  (可以做到阻塞 控制任务的执行顺序)
(2)如果异步任务后面有同步任务  两个任务会并行(同时)执行

 方式1 - 手动创建并发队列:

 dispatch_queue_create(
constchar *label, // 队列名称
dispatch_queue_attr_t attr // 队列的类型
); // 1.创建并发队列 DISPATCH_QUEUE_CONCURRENT (并发)
dispatch_queue_t queue = dispatch_queue_create(“TD", DISPATCH_QUEUE_CONCURRENT); // 2.非ARC需要释放手动创建的队列
dispatch_release(queue); 方式2 - 获取全局并发队列:(GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建) Xcode .2定义方式:
dispatch_get_global_queue(
long identifier, // 队列的优先级
unsignedlong flags // 此参数暂时无用,用0即可
); 全局并发队列的优先级:(级别由高到低) #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_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
2.2.1【代码】同步 + 并发
在实际开发中,同步任务可以保证执行完成之后,才让后续的异步任务开始执行,用于控制任务之间的先后顺序,在后台线程中,处理“用户登录”
 #import "ViewController.h"

 @interface ViewController ()

 @end

 @implementation ViewController

 - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self syncConcurrent];
} #pragma mark - 同步函数 + 并发队列:不会开启新的线程,在当前线程执行任务(主线程),顺序执行,并发队列失去了并发的功能
- (void)syncConcurrent {
NSLog(@"同步并发 ----- begin"); // 1.获得全局的并发队列
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); // 2.将任务加入队列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
}); NSLog(@"同步并发 ----- end");
} #pragma mark - 写法2
- (void)concurrentSync {
// 1. 创建并发队列
dispatch_queue_t conCurrentQueue =
dispatch_queue_create("TD", DISPATCH_QUEUE_CONCURRENT); // 2. 创建任务
void (^task1)() = ^() {
NSLog(@"---task1---%@", [NSThread currentThread]);
}; void (^task2)() = ^() {
NSLog(@"---task2---%@", [NSThread currentThread]);
}; void (^task3)() = ^() {
NSLog(@"---task3---%@", [NSThread currentThread]);
}; // 3. 将同步任务添加到并发队列中
dispatch_sync(conCurrentQueue, task1);
dispatch_sync(conCurrentQueue, task2);
dispatch_sync(conCurrentQueue, task3);
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end 打印结果: -- ::08.387 同步并发[:] 同步并发 ----- begin
-- ::08.387 同步并发[:] -----<NSThread: 0x7fe963d07360>{number = , name = main}
-- ::08.387 同步并发[:] -----<NSThread: 0x7fe963d07360>{number = , name = main}
-- ::08.388 同步并发[:] -----<NSThread: 0x7fe963d07360>{number = , name = main}
-- ::08.388 同步并发[:] 同步并发 ----- end -- ::07.968 同步并发[:] ---task1---<NSThread: 0x7f8e71400d20>{number = , name = main}
-- ::07.969 同步并发[:] ---task2---<NSThread: 0x7f8e71400d20>{number = , name = main}
-- ::07.969 同步并发[:] ---task3---<NSThread: 0x7f8e71400d20>{number = , name = main}
2.2.1【代码】异步 + 并发
 #import "ViewController.h"

 @interface ViewController ()

 @end

 @implementation ViewController

 - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self asyncConcurrent];
} #pragma mark - 异步函数 + 并发队列:可以同时开启多条线程,在当前线程执行任务(主线程),无序执行(按照任务添加到队列中的顺序被调度),线程条数具体由`可调度线程池/底层线程池`来决定
- (void)asyncConcurrent {
NSLog(@"异步并发 ----- begin"); // 1.获得全局的并发队列
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); // 2.将任务加入队列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
}); NSLog(@"异步并发 ----- begin");
} #pragma mark - 写法2
- (void)concurrentAsync {
// 1.创建并发队列
dispatch_queue_t conCurrentQueue =
dispatch_queue_create("TD", DISPATCH_QUEUE_CONCURRENT); // 2. 创建任务
void (^task1)() = ^() {
NSLog(@"---task1---%@", [NSThread currentThread]);
}; void (^task2)() = ^() {
NSLog(@"---task2---%@", [NSThread currentThread]);
}; void (^task3)() = ^() {
NSLog(@"---task3---%@", [NSThread currentThread]);
}; // 3. 将异步任务添加到并发队列中
dispatch_async(conCurrentQueue, task1);
dispatch_async(conCurrentQueue, task2);
dispatch_async(conCurrentQueue, task3);
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end 打印结果: -- ::16.307 异步并发[:] 异步并发 ----- begin
-- ::16.308 异步并发[:] -----<NSThread: 0x7fd598d02490>{number = , name = main}
-- ::16.308 异步并发[:] -----<NSThread: 0x7fd598d02490>{number = , name = main}
-- ::16.308 异步并发[:] -----<NSThread: 0x7fd598d02490>{number = , name = main}
-- ::16.308 异步并发[:] 异步并发 ----- begin -- ::18.557 异步并发[:] ---task2---<NSThread: 0x7fbf68d927b0>{number = , name = (null)}
-- ::18.557 异步并发[:] ---task3---<NSThread: 0x7fbf68e24570>{number = , name = (null)}
-- ::18.557 异步并发[:] ---task1---<NSThread: 0x7fbf68f15ae0>{number = , name = (null)}

2.2.2 创建队列 - 串行队列

 手动创建串行队列:

 dispatch_queue_create(
constchar *label, // 队列名称
dispatch_queue_attr_t attr // 队列的类型
); //1.创建串行队列 //方式1:DISPATCH_QUEUE_SERIAL (串行)
dispatch_queue_t queue = dispatch_queue_create(“TD", DISPATCH_QUEUE_SERIAL);
//方式2:传 NULL
dispatch_queue_t queue = dispatch_queue_create(“TD", NULL); // 2.非ARC需要释放手动创建的队列
dispatch_release(queue);

  • 串行队列中的任务都是按顺序执行,谁在前就先执行谁
  • 主线程和子线程平等,一样谁在前选执行谁
  • 执行完一个才会执行下一个任务
 2.2.2【代码】同步 + 串行

  • 串行队列中的任务都是按顺序执行,谁在前就先执行谁
  • 主线程和子线程平等,一样谁在前选执行谁
  • 执行完一个才会执行下一个任务
 #import "ViewController.h"

 @interface ViewController ()

 @end

 @implementation ViewController

 - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self syncSerial];
} #pragma mark - 同步函数 + 串行队列:不会开启新的线程,在当前线程执行任务(主线程),任务是串行的(顺序执行)
- (void)syncSerial {
NSLog(@"同步串行 ----- begin"); // 1.创建串行队列
dispatch_queue_t queue = dispatch_queue_create("TD", DISPATCH_QUEUE_SERIAL); // 2.将任务加入队列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
}); NSLog(@"同步串行 ----- end");
} #pragma mark - 写法2
- (void)serialSyncDemo {
// 1.创建队列
dispatch_queue_t serialQueue =
dispatch_queue_create("TD", DISPATCH_QUEUE_SERIAL); // 2.创建任务
void (^task1)() = ^() {
NSLog(@"task1---%@", [NSThread currentThread]);
}; void (^task2)() = ^() {
NSLog(@"task2---%@", [NSThread currentThread]);
}; void (^task3)() = ^() {
NSLog(@"task3---%@", [NSThread currentThread]);
}; // 3.将同步任务,添加到串行队列
dispatch_sync(serialQueue, task3);
dispatch_sync(serialQueue, task1);
dispatch_sync(serialQueue, task2);
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end 打印结果: -- ::13.648 同步串行[:] 同步串行 ----- begin
-- ::13.649 同步串行[:] -----<NSThread: 0x7fab52f04910>{number = , name = main}
-- ::13.649 同步串行[:] -----<NSThread: 0x7fab52f04910>{number = , name = main}
-- ::13.649 同步串行[:] -----<NSThread: 0x7fab52f04910>{number = , name = main}
-- ::13.649 同步串行[:] 同步串行 ----- end -- ::53.272 同步串行[:] task1---<NSThread: 0x7fd910c05150>{number = , name = main}
-- ::53.273 同步串行[:] task2---<NSThread: 0x7fd910c05150>{number = , name = main}
-- ::53.273 同步串行[:] task3---<NSThread: 0x7fd910c05150>{number = , name = main}
2.2.2【代码】异步 + 串行

  • 串行队列中的任务都是按顺序执行,谁在前就先执行谁
  • 主线程和子线程平等,一样谁在前选执行谁
  • 执行完一个才会执行下一个任务
串行队列,异步任务,在多线程中,是斯坦福大学最推荐的一种多线程方式!
  • 优点:将任务放在其他线程中工作,每个任务顺序执行,便于调试
  • 缺点:并发能力不强,最多只能使用一条线程!
 #import "ViewController.h"

 @interface ViewController ()

 @end

 @implementation ViewController

 - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self asyncSerial];
} #pragma mark - 异步函数 + 串行队列:会开启新的线程,在子线程执行任务,任务是串行的(顺序执行),只开一条线程
- (void)asyncSerial {
NSLog(@"异步串行 ----- begin");
NSLog(@"主线程 ----- %@", [NSThread mainThread]); // 1.创建串行队列
//写法1:
dispatch_queue_t queue = dispatch_queue_create("TD", DISPATCH_QUEUE_SERIAL);
//写法2:
dispatch_queue_t queue = dispatch_queue_create("TD", NULL); // 2.将任务加入队列
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
}); NSLog(@"异步串行 ----- end");
} #pragma mark - 写法2
- (void)serialAsyncDemo {
// 1.创建队列
dispatch_queue_t serialQueue =
dispatch_queue_create("TD", DISPATCH_QUEUE_SERIAL); // 2.创建任务
void (^task1)() = ^() {
NSLog(@"task1---%@", [NSThread currentThread]);
}; void (^task2)() = ^() {
NSLog(@"task2---%@", [NSThread currentThread]);
}; void (^task3)() = ^() {
NSLog(@"task3---%@", [NSThread currentThread]);
}; // 3.将异步任务添加到串行队列
dispatch_async(serialQueue, task1);
dispatch_async(serialQueue, task2);
dispatch_async(serialQueue, task3);
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end 打印结果: -- ::38.392 异步串行[:] 异步串行 ----- begin
-- ::38.393 异步串行[:] 主线程 ----- <NSThread: 0x7ff271701ba0>{number = , name = main}
-- ::38.393 异步串行[:] 异步串行 ----- end
-- ::38.393 异步串行[:] -----<NSThread: 0x7ff2717abb30>{number = , name = (null)}
-- ::38.394 异步串行[:] -----<NSThread: 0x7ff2717abb30>{number = , name = (null)}
-- ::38.394 异步串行[:] -----<NSThread: 0x7ff2717abb30>{number = , name = (null)} -- ::21.844 异步串行[:] task1---<NSThread: 0x7fddb9405f40>{number = , name = (null)}
-- ::21.845 异步串行[:] task2---<NSThread: 0x7fddb9405f40>{number = , name = (null)}
-- ::21.845 异步串行[:] task3---<NSThread: 0x7fddb9405f40>{number = , name = (null)}

2.2.3 创建队列 - 主队列(特殊的串行队列)

主队列(跟主线程相关联的队列):
  • 主队列是GCD自带的一种特殊的串行队列
  • 放在主队列中的任务,都会放到主线程中执行
  • 如果把任务放到主队列中进行处理,那么不论处理函数是异步的还是同步的都不会开启新的线程。
  • 主队列中不能用同步任务,无论是在异步任务前还是后都会死锁
 获取主队列:

 dispatch_get_main_queue(void);

 dispatch_queue_t queue = dispatch_get_main_queue();

2.2.3【代码】同步 + 主队列

主队列中不能用同步任务,无论是在异步任务前还是后都会死锁

 #import "ViewController.h"

 @interface ViewController ()

 @end

 @implementation ViewController

 - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self syncMain];
} #pragma mark - 同步函数 + 主队列:不会开启新的线程,会出现"死等",可能导致`主线程`卡死
- (void)syncMain {
NSLog(@"同步主队列 ----- begin"); // 1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue(); // 2.将任务加入队列
dispatch_sync(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
}); NSLog(@"同步主队列 ----- end");
} - (void)mainQueueSync {
NSLog(@"同步主队列 ----- begin"); // 1.获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue(); // 2.创建队列
void (^task1)() = ^() {
NSLog(@"---task1---%@", [NSThread currentThread]);
}; void (^task2)() = ^() {
NSLog(@"---task2---%@", [NSThread currentThread]);
}; void (^task3)() = ^() {
NSLog(@"---task3---%@", [NSThread currentThread]);
}; // 3.将同步任务添加到并发队列中
dispatch_sync(mainQueue, task1);
dispatch_sync(mainQueue, task2);
dispatch_sync(mainQueue, task3); NSLog(@"同步主队列 ----- end");
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end 打印结果: -- ::55.594 同步主队列[:] 同步主队列 ----- begin

 

2.3【代码】异步 + 主队列

 #import "ViewController.h"

 @interface ViewController ()

 @end

 @implementation ViewController

 - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self asyncMain];
} #pragma mark - 异步函数 + 主队列:不会开启新的线程,在当前线程执行任务(主线程),任务是串行的(顺序执行),只开一条线程(适合处理 UI 或者是 UI事件)
- (void)asyncMain {
NSLog(@"异步主队列 ----- begin"); // 1.获得主队列
dispatch_queue_t queue = dispatch_get_main_queue(); // 2.将任务加入队列
dispatch_async(queue, ^{
NSLog(@"1-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"2-----%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"3-----%@", [NSThread currentThread]);
}); NSLog(@"异步主队列 ----- end");
} #pragma mark - 写法2
- (void)mainQueueAsync {
NSLog(@"异步主队列 ----- begin"); // 1.获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue(); // 2.创建任务
void (^task1)() = ^() {
NSLog(@"---async task1---%@", [NSThread currentThread]);
}; void (^task2)() = ^() {
NSLog(@"---async task2---%@", [NSThread currentThread]);
}; void (^task3)() = ^() {
NSLog(@"---async task3---%@", [NSThread currentThread]);
}; // 3.将异步任务添加到主队列中
dispatch_async(mainQueue, task1);
dispatch_async(mainQueue, task2);
dispatch_async(mainQueue, task3); NSLog(@"异步主队列 ----- end");
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end 打印结果: -- ::47.663 异步主队列[:] 异步主队列 ----- begin
-- ::47.663 异步主队列[:] 异步主队列 ----- end
-- ::47.664 异步主队列[:] -----<NSThread: 0x7feec9c082e0>{number = , name = main}
-- ::47.664 异步主队列[:] -----<NSThread: 0x7feec9c082e0>{number = , name = main}
-- ::47.664 异步主队列[:] -----<NSThread: 0x7feec9c082e0>{number = , name = main} -- ::15.690 异步主队列[:] 异步主队列 ----- begin
-- ::15.691 异步主队列[:] 异步主队列 ----- end
-- ::15.691 异步主队列[:] ---async task1---<NSThread: 0x7f9de1c074e0>{number = , name = main}
-- ::15.691 异步主队列[:] ---async task2---<NSThread: 0x7f9de1c074e0>{number = , name = main}
-- ::15.692 异步主队列[:] ---async task3---<NSThread: 0x7f9de1c074e0>{number = , name = main}

2.2.4 总结

GCD 队列类型的创建方式:

  • 并发队列:手动创建、全局
  • 串行队列:手动创建、主队列            

注意:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(比如同步主队列) 
 

同步函数:无论是什么队列都不会开启线程
(1)并发队列:不会开线程
(2)串行队列:不会开线程
 
异步函数:具备开启线程的能力(但不一定会开线程 ),开启几条线程由队列决定
(1)并发队列:能开启N条线程
(2)串行队列:开启1条线程
 

(1)

同步函数 + 并发队列:不会开启新的线程,在当前线程执行任务(主线程),顺序执行,并发队列失去了并发的功能
 
异步函数 + 并发队列:可以同时开启多条线程,在当前线程执行任务(主线程),无序执行(按照任务添加到队列中的顺序被调度),线程条数具体由`可调度线程池/底层线程池`来决定
 
并行队列中的任务是多个任务同时执行的  :
(1)如果异步任务前面有同步任务 就会先执行同步任务同步任务是按顺序执行的任务等他执行完了才会执行并行中的异步任务  (可以做到阻塞 控制任务的执行顺序)
(2)如果异步任务后面有同步任务  两个任务会并行(同时)执行
 

(2)

同步函数 + 串行队列:不会开启新的线程,在当前线程执行任务(主线程),任务是串行的(顺序执行)
 
异步函数 + 串行队列:会开启新的线程,在子线程执行任务,任务是串行的(顺序执行),只开一条线程
 

(3) 

异步函数 + 主队列:不会开启新的线程,在当前线程执行任务(主线程),任务是串行的(顺序执行),只开一条线程(适合处理 UI 或者是 UI事件)
 
同步函数 + 主队列:不会开启新的线程,会出现"死等",可能导致`主线程`卡死
 
主队列中不能用同步任务,无论是在异步任务前还是后都会死锁 
 

 
3.0 线程间的通信
 #import "ViewController.h"

 @interface ViewController ()
@property(weak, nonatomic) IBOutlet UIImageView *imageView;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 全局的异步并发
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{ // 图片的网络路径
NSURL *url = [NSURL
URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"]; // 加载图片
NSData *data = [NSData dataWithContentsOfURL:url]; // 生成图片
UIImage *image = [UIImage imageWithData:data]; // 回到主线程,执行 UI 刷新操作
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

图片下载示例

4.1 其他用法 - barrier函数

 #import "ViewController.h"

 @interface ViewController ()
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 这里使用全局并发队列的方式会导致 dispatch_barrier_async 功能失效
dispatch_queue_t queue =
dispatch_queue_create("TD", DISPATCH_QUEUE_CONCURRENT); 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]);
});
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end 打印结果: -- ::16.214 其他用法 - barrier函数[:] ---------<NSThread: 0x7fc4a0c2c5d0>{number = , name = (null)}
-- ::16.214 其他用法 - barrier函数[:] ---------<NSThread: 0x7fc4a0f0cb20>{number = , name = (null)}
-- ::16.214 其他用法 - barrier函数[:] ----barrier-----<NSThread: 0x7fc4a0f0cb20>{number = , name = (null)}
-- ::16.215 其他用法 - barrier函数[:] ---------<NSThread: 0x7fc4a0f0cb20>{number = , name = (null)}
-- ::16.215 其他用法 - barrier函数[:] ---------<NSThread: 0x7fc4a0c2c5d0>{number = , name = (null)}

4.2 其他用法 - 延迟执行

 方法1:调用NSObject的方法

 // 该方法在那个线程调用,那么run就在哪个线程执行(当前线程),通常是主线程
// 2秒后再调用self的run方法
[selfperformSelector:@selector(run) withObject:nilafterDelay:2.0]; 方法2:使用GCD函数 // 这里是在主线程执行,如果想要在子线程执行,选择相应的队列
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2秒后异步执行这里的代码...
}); 方法3:使用NSTimer [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(run)
userInfo:nil
repeats:NO];
 #import "ViewController.h"

 @interface ViewController ()

 @end

 @implementation ViewController

 - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self delay1];
} #pragma mark - 方法1:调用NSObject的方法
- (void)delay1 {
NSLog(@"touchesBegan-----"); // 2秒后再调用self的run方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
} #pragma mark - 方法2:使用 GCD 函数
- (void)delay2 {
NSLog(@"touchesBegan-----"); // 这里是在主线程执行,如果想要在子线程执行,选择相应的队列
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
NSLog(@"run-----");
});
} #pragma mark - 方法3:使用NSTimer定时器
- (void)delay3 {
NSLog(@"touchesBegan-----"); [NSTimer scheduledTimerWithTimeInterval:2.0
target:self
selector:@selector(run)
userInfo:nil
repeats:NO];
} - (void)run {
NSLog(@"run-----");
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end 打印结果: -- ::56.384 其他用法 - 延迟执行[:] touchesBegan-----
-- ::58.385 其他用法 - 延迟执行[:] run-----

4.3 其他用法 - 一次性代码

 使用dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,适合做资源的加载

 static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行1次的代码(这里面默认是线程安全的)
});
}

4.4 其他用法 - 快速迭代

 使用dispatch_apply函数能进行快速迭代遍历:

 dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t queue#>, ^(size_t) {
// 代码
}); dispatch_apply(, dispatch_get_global_queue(, ), ^(size_t index){
// 执行10次代码,index顺序不确定
}); 
 #import "ViewController.h"

 @interface ViewController ()

 @end

 @implementation ViewController

 - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self apply];
} #pragma mark - 文件剪切方法1:快速迭代
- (void)apply { dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); NSString *from = @"/Users/TD/Desktop/From";
NSString *to = @"/Users/TD/Desktop/To"; NSFileManager *mgr = [NSFileManager defaultManager];
NSArray *subpaths = [mgr subpathsAtPath:from]; dispatch_apply(subpaths.count, queue, ^(size_t index) { NSString *subpath = subpaths[index];
NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
NSString *toFullpath = [to stringByAppendingPathComponent:subpath]; // 剪切
[mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil]; NSLog(@"%@---%@", [NSThread currentThread], subpath);
});
} #pragma mark - 文件剪切方法2:传统方式
- (void)moveFile {
NSString *from = @"/Users/TD/Desktop/From";
NSString *to = @"/Users/TD/Desktop/To"; NSFileManager *mgr = [NSFileManager defaultManager]; //获取文件夹下的所有文件路径,包括子文件夹下的文件路径
NSArray *subpaths = [mgr subpathsAtPath:from]; for (NSString *subpath in subpaths) { //全路径
NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
NSString *toFullpath = [to stringByAppendingPathComponent:subpath]; dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
// 剪切
[mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
});
}
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

文件剪切示例

4.5 其他用法 - 队列组/调度组

有这么一种需求:

首先:分别异步执行2个耗时的操作
其次:等2个异步操作都执行完毕后,再回到主线程执行操作 
 
解决办法:用队列组,也叫做调度组
 // 创建一个队列组
dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
// 执行1个耗时的异步操作
}); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{
// 执行1个耗时的异步操作
}); dispatch_group_notify(group, dispatch_get_main_queue(),
^{
// 等前面的异步操作都执行完毕后,回到主线程...
});
 #import "ViewController.h"

 @interface ViewController ()
@property(weak, nonatomic) IBOutlet UIImageView *imageView;
@property(nonatomic, strong) UIImage *image1; //图片1
@property(nonatomic, strong) UIImage *image2; //图片2
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
} - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); // 创建一个队列组
dispatch_group_t group = dispatch_group_create(); // 1.下载图片1
dispatch_group_async(group, queue, ^{
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成图片
self.image1 = [UIImage imageWithData:data];
}); // 2.下载图片2
dispatch_group_async(group, queue, ^{
// 图片的网络路径
NSURL *url = [NSURL URLWithString: @"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成图片
self.image2 = [UIImage imageWithData:data];
}); // 3.将图片1、图片2合成一张新的图片(也可以直接在此处回到主线程,只不过是因为绘制图片比较耗时,没有放在主线程而已)
dispatch_group_notify(group, queue, ^{ // 开启新的图形上下文
UIGraphicsBeginImageContext(CGSizeMake(, )); // 绘制图片
[self.image1 drawInRect:CGRectMake(, , , )];
[self.image2 drawInRect:CGRectMake(, , , )]; // 取得上下文中的图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); // 结束上下文
UIGraphicsEndImageContext(); // 回到主线程显示图片
dispatch_async(dispatch_get_main_queue(), ^{
// 4.将新图片显示出来
self.imageView.image = image;
});
});
} - (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
} @end

图片下载后合成示例

大致概况如下:

5.0 GCD 的定时器事件

实际上 RunLoop 底层也会用到 GCD 的东西,比如 RunLoop 是用 dispatch_source_t 实现的 Timer。但同时 GCD 提供的某些接口也用到了 RunLoop, 例如 dispatch_async()。
 
当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。 
 
【区别】NSTimer & GCD 的定时器:

  • CFRunLoopTimerRef 基本上说的就是 NSTimer,它受 RunLoop 的 Mode 影响
  • GCD 的定时器不受 RunLoop 的 Mode 影响

代码示例:当滚动文字的时候,是不会影响 GCD的定时器的

 #import "ViewController.h"

 @interface ViewController ()
// 定时器 (这里不用带*,因为dispatch_source_t就是个类,内部已经包含了*)
@property(nonatomic, strong) dispatch_source_t timer;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
} int count = ;
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 1、获得队列
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_queue_t queue = dispatch_get_main_queue(); // 2、创建一个定时器 (dispatch_source_t本质还是个OC对象)
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, , , queue); // 3、设置定时器的各种属性(几时开始任务,每隔多长时间执行一次) // 触发时间(何时开始执行第一个任务)
// 比当前时间晚1秒
dispatch_time_t start =
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
// 马上就执行
// dispatch_time_t start1 = DISPATCH_TIME_NOW; // 时间间隔。GCD的时间参数,一般是纳秒(1秒 == 10的9次方纳秒)
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC); // 参数:(1)定时器名字 (2)触发时间 (3)时间间隔 (4)0
dispatch_source_set_timer(self.timer, start, interval, ); // 4、设置定时器的回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------------%@", [NSThread currentThread]); count++; if (count == ) {
// 取消定时器
dispatch_cancel(self.timer);
self.timer = nil;
}
}); // 5、启动定时器
dispatch_resume(self.timer);
}
@end 打印结果: -- ::39.066 -掌握-GCD定时器[:] ------------<NSThread: 0x7f8af0705770>{number = , name = main}
-- ::40.067 -掌握-GCD定时器[:] ------------<NSThread: 0x7f8af0705770>{number = , name = main}
-- ::41.066 -掌握-GCD定时器[:] ------------<NSThread: 0x7f8af0705770>{number = , name = main}
-- ::42.067 -掌握-GCD定时器[:] ------------<NSThread: 0x7f8af0705770>{number = , name = main}

如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^
 
 
作者:蓝田(Loto)
出处:http://www.cnblogs.com/shorfng/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接。
 

如有疑问,请发送邮件至 shorfng@126.com 联系我。
 
 

 

 
 

4.3 多线程进阶篇<中>(GCD)的更多相关文章

  1. 4.4 多线程进阶篇<下>(NSOperation)

    本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人"简书" 本文源码 Demo 详见 Github https://github.c ...

  2. 4.1/4.2 多线程进阶篇<上>(Pthread & NSThread)

    本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人 “简书” 本文源码 Demo 详见 Githubhttps://github.com/shorfng ...

  3. ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase玩转URL

    http://www.cnblogs.com/John-Connor/archive/2012/05/03/2478821.html 引言-- 在初级篇中,我们介绍了如何利用基于ASP.NET MVC ...

  4. [转载]ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase玩转URL

    引言-- 在初级篇中,我们介绍了如何利用基于ASP.NET MVC的Web程序中的Global文件来简单的重写路由.也介绍了它本身的局限性-依赖于路由信息中的键值对: 如果键值对中没有的值,我们无法将 ...

  5. ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase

    原文地址:http://www.51csharp.com/MVC/882.html   ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase玩转URL 引言-- 在初级篇中,我们 ...

  6. Spring+SpringMVC+MyBatis整合进阶篇(四)RESTful实战(前端代码修改)

    前言 前文<RESTful API实战笔记(接口设计及Java后端实现)>中介绍了RESTful中后端开发的实现,主要是接口地址修改和返回数据的格式及规范的修改,本文则简单介绍一下,RES ...

  7. Java进阶篇(六)——Swing程序设计(上)

    Swing是GUI(图形用户界面)开发工具包,内容有很多,这里会分块编写,但在进阶篇中只编写Swing中的基本要素,包括容器.组件和布局等,更深入的内容会在高级篇中出现.想深入学习的朋友们可查阅有关资 ...

  8. [转]ASP.NET MVC URL重写与优化(进阶篇)-继承RouteBase玩转URL

    本文转自:http://www.cnblogs.com/John-Connor/archive/2012/05/03/2478821.html 引言-- 在初级篇中,我们介绍了如何利用基于ASP.NE ...

  9. springcloud-zuul进阶篇

    一 前言 经过zuul初级篇(博客或者公主号springcloud专栏可以找到)的学习,读者都懂得如何简单的使用zuul进行路由网关配置,在进阶篇中你将获得zuul核心功能过滤器的基本使用,通过zuu ...

随机推荐

  1. Dom4j解析xml文件

    dom4j是一个Java的XML API,类似于jdom,用来读取的XML文件,由于它是将文件解析完存放在内存当中的,所以不适合解析大的XML文件,但就方便性和性能方面,一定程度要优于JDK中Domc ...

  2. JS数组求最大值和最小值

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  3. 我的攒机(zuosi)过程

    先贴上自己的配置清单: CPU E3 1240V2 930 https://item.taobao.com/item.htm?spm=a230r.1.14.8.lds6QF&id=532971 ...

  4. ubuntu 15.10 安装swift开发环境 2016/4/17

    ubuntu 15.10 64位 下载地址 https://swift.org/download/#using-downloads 1.首先在ubuntu终端上 (ctl+alt+t打开) 下载cla ...

  5. Redis启动多端口、运行多实例

    默认Redis程序安装在/usr/local/redis目录下: 配置文件:/usr/local/redis/redis.conf,该配置文件中配置的端口为默认端口:6379: Redis的启动命令路 ...

  6. WebService -- Java 实现之 CXF ( 添加系统预定义的拦截器)

    1. 概述 CXF允许我们在webservice的in/out位置添加拦截器.拦截器有两大分类,一类是系统预定义的:另一类是自定义拦截器. 2. 在server端添加拦截器. JaxWsServerF ...

  7. Js/Jquery获取iframe中的元素

    转载: Js/Jquery获取iframe中的元素 - - ITeye技术网站http://java-my-life.iteye.com/blog/1275205 在web开发中,经常会用到ifram ...

  8. 初入水:vector

    ---恢复内容开始---Vector 是一个类模板.不是一种数据类型. Vector<int>是一种数据类型 类的作用,是一种顺序容器,支持随机访问,可动态分配空间(扩充:销毁旧内存,更新 ...

  9. .net MVC 简单图片上传

    主要完成的是在网页上 上传一张图片到服务器 我搜出来的上传文件代码都特别复杂,对于初学者来说,先解决能上传的问题才最重要,并不需要特别多的功能,仅适合不会上传的初学者,大神请绕路,错误请指出,谢谢 v ...

  10. wps恢复经典模式

    经典模式 情况一: 恢复的方法:点击红色区域 情况二: 恢复的方法:点击红色区域