zoukankan      html  css  js  c++  java
  • 4.3 多线程进阶篇<中>(GCD)

    更正:队列名称的作用的图中,箭头标注的有些问题,已修正
    本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末
    如果觉得本文内容过长,请前往本人 “简书
     
    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 进行执行。
     11)任务的执行:同步
     2 
     3 //queue:队列    block:任务
     4 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
     5 
     62)任务的执行:异步
     7 
     8 - 函数1
     9 
    10 //queue:队列    block:任务
    11 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    12 
    13 - 函数2
    14 
    15 // 在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
    16 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:
    • 凡是函数名种带有createcopy ew etain等字眼, 都应该在不需要使用这个数据的时候进行release
    • GCD的数据类型在ARC环境下不需要再做release         dispatch_release(queue); // 非ARC需要释放手动创建的队列
    • CF(Core Foundation)的数据类型在ARC环境下还是需要再做release
     
    队列名称的作用:将来调试的时候,可以看得出任务是在哪个队列中执行的。
     
    2.2.1 创建队列 - 并发队列
    并行队列中的任务是多个任务同时执行的  :
    (1)如果异步任务前面有同步任务 就会先执行同步任务同步任务是按顺序执行的任务等他执行完了才会执行并行中的异步任务  (可以做到阻塞 控制任务的执行顺序)
    (2)如果异步任务后面有同步任务  两个任务会并行(同时)执行
     
     1 方式1 - 手动创建并发队列:
     2 
     3 dispatch_queue_create(
     4     constchar *label,           // 队列名称
     5     dispatch_queue_attr_t attr  // 队列的类型
     6 );
     7 
     8 // 1.创建并发队列   DISPATCH_QUEUE_CONCURRENT (并发)
     9 dispatch_queue_t queue = dispatch_queue_create(“TD", DISPATCH_QUEUE_CONCURRENT);
    10 
    11 // 2.非ARC需要释放手动创建的队列
    12 dispatch_release(queue);
    13 
    14 方式2 - 获取全局并发队列:(GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建)
    15 
    16 Xcode 7.2定义方式:
    17 dispatch_get_global_queue(
    18        long identifier,    // 队列的优先级
    19        unsignedlong flags  // 此参数暂时无用,用0即可
    20 );
    21 
    22 全局并发队列的优先级:(级别由高到低)
    23 
    24 #define DISPATCH_QUEUE_PRIORITY_HIGH 2               //
    25 #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0            // 默认(中)
    26 #define DISPATCH_QUEUE_PRIORITY_LOW (-2)             //
    27 #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
    28 
    29 // 获取全局并发队列
    30 dispatch_queue_t queue =
    31       dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    2.2.1【代码】同步 + 并发
    在实际开发中,同步任务可以保证执行完成之后,才让后续的异步任务开始执行,用于控制任务之间的先后顺序,在后台线程中,处理“用户登录”
     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 
     5 @end
     6 
     7 @implementation ViewController
     8 
     9 - (void)viewDidLoad {
    10   [super viewDidLoad];
    11   // Do any additional setup after loading the view, typically from a nib.
    12 }
    13 
    14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    15   [self syncConcurrent];
    16 }
    17 
    18 #pragma mark - 同步函数 + 并发队列:不会开启新的线程,在当前线程执行任务(主线程),顺序执行,并发队列失去了并发的功能
    19 - (void)syncConcurrent {
    20   NSLog(@"同步并发 ----- begin");
    21 
    22   // 1.获得全局的并发队列
    23   dispatch_queue_t queue =
    24       dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    25 
    26   // 2.将任务加入队列
    27   dispatch_sync(queue, ^{
    28     NSLog(@"1-----%@", [NSThread currentThread]);
    29   });
    30   dispatch_sync(queue, ^{
    31     NSLog(@"2-----%@", [NSThread currentThread]);
    32   });
    33   dispatch_sync(queue, ^{
    34     NSLog(@"3-----%@", [NSThread currentThread]);
    35   });
    36 
    37   NSLog(@"同步并发 ----- end");
    38 }
    39 
    40 #pragma mark - 写法2
    41 - (void)concurrentSync {
    42   // 1. 创建并发队列
    43   dispatch_queue_t conCurrentQueue =
    44       dispatch_queue_create("TD", DISPATCH_QUEUE_CONCURRENT);
    45 
    46   // 2. 创建任务
    47   void (^task1)() = ^() {
    48     NSLog(@"---task1---%@", [NSThread currentThread]);
    49   };
    50 
    51   void (^task2)() = ^() {
    52     NSLog(@"---task2---%@", [NSThread currentThread]);
    53   };
    54 
    55   void (^task3)() = ^() {
    56     NSLog(@"---task3---%@", [NSThread currentThread]);
    57   };
    58 
    59   // 3. 将同步任务添加到并发队列中
    60   dispatch_sync(conCurrentQueue, task1);
    61   dispatch_sync(conCurrentQueue, task2);
    62   dispatch_sync(conCurrentQueue, task3);
    63 }
    64 
    65 - (void)didReceiveMemoryWarning {
    66   [super didReceiveMemoryWarning];
    67   // Dispose of any resources that can be recreated.
    68 }
    69 
    70 @end
    71 
    72 打印结果:
    73 
    74 2016-03-20 00:04:08.387 同步并发[1702:246074] 同步并发 ----- begin
    75 2016-03-20 00:04:08.387 同步并发[1702:246074] 1-----<NSThread: 0x7fe963d07360>{number = 1, name = main}
    76 2016-03-20 00:04:08.387 同步并发[1702:246074] 2-----<NSThread: 0x7fe963d07360>{number = 1, name = main}
    77 2016-03-20 00:04:08.388 同步并发[1702:246074] 3-----<NSThread: 0x7fe963d07360>{number = 1, name = main}
    78 2016-03-20 00:04:08.388 同步并发[1702:246074] 同步并发 ----- end
    79 
    80 2016-03-20 00:05:07.968 同步并发[1724:247291] ---task1---<NSThread: 0x7f8e71400d20>{number = 1, name = main}
    81 2016-03-20 00:05:07.969 同步并发[1724:247291] ---task2---<NSThread: 0x7f8e71400d20>{number = 1, name = main}
    82 2016-03-20 00:05:07.969 同步并发[1724:247291] ---task3---<NSThread: 0x7f8e71400d20>{number = 1, name = main}
    View Code
    2.2.1【代码】异步 + 并发
     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 
     5 @end
     6 
     7 @implementation ViewController
     8 
     9 - (void)viewDidLoad {
    10   [super viewDidLoad];
    11   // Do any additional setup after loading the view, typically from a nib.
    12 }
    13 
    14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    15   [self asyncConcurrent];
    16 }
    17 
    18 #pragma mark - 异步函数 + 并发队列:可以同时开启多条线程,在当前线程执行任务(主线程),无序执行(按照任务添加到队列中的顺序被调度),线程条数具体由`可调度线程池/底层线程池`来决定
    19 - (void)asyncConcurrent {
    20   NSLog(@"异步并发 ----- begin");
    21 
    22   // 1.获得全局的并发队列
    23   dispatch_queue_t queue =
    24       dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    25 
    26   // 2.将任务加入队列
    27   dispatch_sync(queue, ^{
    28     NSLog(@"1-----%@", [NSThread currentThread]);
    29   });
    30   dispatch_sync(queue, ^{
    31     NSLog(@"2-----%@", [NSThread currentThread]);
    32   });
    33   dispatch_sync(queue, ^{
    34     NSLog(@"3-----%@", [NSThread currentThread]);
    35   });
    36 
    37   NSLog(@"异步并发 ----- begin");
    38 }
    39 
    40 #pragma mark - 写法2
    41 - (void)concurrentAsync {
    42   // 1.创建并发队列
    43   dispatch_queue_t conCurrentQueue =
    44       dispatch_queue_create("TD", DISPATCH_QUEUE_CONCURRENT);
    45 
    46   // 2. 创建任务
    47   void (^task1)() = ^() {
    48     NSLog(@"---task1---%@", [NSThread currentThread]);
    49   };
    50 
    51   void (^task2)() = ^() {
    52     NSLog(@"---task2---%@", [NSThread currentThread]);
    53   };
    54 
    55   void (^task3)() = ^() {
    56     NSLog(@"---task3---%@", [NSThread currentThread]);
    57   };
    58 
    59   // 3. 将异步任务添加到并发队列中
    60   dispatch_async(conCurrentQueue, task1);
    61   dispatch_async(conCurrentQueue, task2);
    62   dispatch_async(conCurrentQueue, task3);
    63 }
    64 
    65 - (void)didReceiveMemoryWarning {
    66   [super didReceiveMemoryWarning];
    67   // Dispose of any resources that can be recreated.
    68 }
    69 
    70 @end
    71 
    72 打印结果:
    73 
    74 2016-03-20 00:11:16.307 异步并发[1879:254274] 异步并发 ----- begin
    75 2016-03-20 00:11:16.308 异步并发[1879:254274] 1-----<NSThread: 0x7fd598d02490>{number = 1, name = main}
    76 2016-03-20 00:11:16.308 异步并发[1879:254274] 2-----<NSThread: 0x7fd598d02490>{number = 1, name = main}
    77 2016-03-20 00:11:16.308 异步并发[1879:254274] 3-----<NSThread: 0x7fd598d02490>{number = 1, name = main}
    78 2016-03-20 00:11:16.308 异步并发[1879:254274] 异步并发 ----- begin
    79 
    80 2016-03-20 00:18:18.557 异步并发[1945:260502] ---task2---<NSThread: 0x7fbf68d927b0>{number = 3, name = (null)}
    81 2016-03-20 00:18:18.557 异步并发[1945:260628] ---task3---<NSThread: 0x7fbf68e24570>{number = 4, name = (null)}
    82 2016-03-20 00:18:18.557 异步并发[1945:260503] ---task1---<NSThread: 0x7fbf68f15ae0>{number = 2, name = (null)}
    View Code

    2.2.2 创建队列 - 串行队列

     1 手动创建串行队列:
     2 
     3 dispatch_queue_create(
     4     constchar *label,           // 队列名称
     5     dispatch_queue_attr_t attr  // 队列的类型
     6 );
     7 
     8 //1.创建串行队列
     9 
    10 //方式1:DISPATCH_QUEUE_SERIAL (串行)
    11 dispatch_queue_t queue = dispatch_queue_create(“TD", DISPATCH_QUEUE_SERIAL);
    12 //方式2:传 NULL
    13 dispatch_queue_t queue = dispatch_queue_create(“TD", NULL);
    14 
    15 // 2.非ARC需要释放手动创建的队列
    16 dispatch_release(queue);

    • 串行队列中的任务都是按顺序执行,谁在前就先执行谁
    • 主线程和子线程平等,一样谁在前选执行谁
    • 执行完一个才会执行下一个任务
     
     2.2.2【代码】同步 + 串行
    • 串行队列中的任务都是按顺序执行,谁在前就先执行谁
    • 主线程和子线程平等,一样谁在前选执行谁
    • 执行完一个才会执行下一个任务
     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 
     5 @end
     6 
     7 @implementation ViewController
     8 
     9 - (void)viewDidLoad {
    10   [super viewDidLoad];
    11   // Do any additional setup after loading the view, typically from a nib.
    12 }
    13 
    14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    15   [self syncSerial];
    16 }
    17 
    18 #pragma mark - 同步函数 + 串行队列:不会开启新的线程,在当前线程执行任务(主线程),任务是串行的(顺序执行)
    19 - (void)syncSerial {
    20   NSLog(@"同步串行 ----- begin");
    21 
    22   // 1.创建串行队列
    23   dispatch_queue_t queue = dispatch_queue_create("TD", DISPATCH_QUEUE_SERIAL);
    24 
    25   // 2.将任务加入队列
    26   dispatch_sync(queue, ^{
    27     NSLog(@"1-----%@", [NSThread currentThread]);
    28   });
    29   dispatch_sync(queue, ^{
    30     NSLog(@"2-----%@", [NSThread currentThread]);
    31   });
    32   dispatch_sync(queue, ^{
    33     NSLog(@"3-----%@", [NSThread currentThread]);
    34   });
    35 
    36   NSLog(@"同步串行 ----- end");
    37 }
    38 
    39 #pragma mark - 写法2
    40 - (void)serialSyncDemo {
    41   // 1.创建队列
    42   dispatch_queue_t serialQueue =
    43       dispatch_queue_create("TD", DISPATCH_QUEUE_SERIAL);
    44 
    45   // 2.创建任务
    46   void (^task1)() = ^() {
    47     NSLog(@"task1---%@", [NSThread currentThread]);
    48   };
    49 
    50   void (^task2)() = ^() {
    51     NSLog(@"task2---%@", [NSThread currentThread]);
    52   };
    53 
    54   void (^task3)() = ^() {
    55     NSLog(@"task3---%@", [NSThread currentThread]);
    56   };
    57 
    58   // 3.将同步任务,添加到串行队列
    59   dispatch_sync(serialQueue, task3);
    60   dispatch_sync(serialQueue, task1);
    61   dispatch_sync(serialQueue, task2);
    62 }
    63 
    64 - (void)didReceiveMemoryWarning {
    65   [super didReceiveMemoryWarning];
    66   // Dispose of any resources that can be recreated.
    67 }
    68 
    69 @end
    70 
    71 打印结果:
    72 
    73 2016-03-20 00:38:13.648 同步串行[2145:276628] 同步串行 ----- begin
    74 2016-03-20 00:38:13.649 同步串行[2145:276628] 1-----<NSThread: 0x7fab52f04910>{number = 1, name = main}
    75 2016-03-20 00:38:13.649 同步串行[2145:276628] 2-----<NSThread: 0x7fab52f04910>{number = 1, name = main}
    76 2016-03-20 00:38:13.649 同步串行[2145:276628] 3-----<NSThread: 0x7fab52f04910>{number = 1, name = main}
    77 2016-03-20 00:38:13.649 同步串行[2145:276628] 同步串行 ----- end
    78 
    79 2016-03-20 00:47:53.272 同步串行[2248:284920] task1---<NSThread: 0x7fd910c05150>{number = 1, name = main}
    80 2016-03-20 00:47:53.273 同步串行[2248:284920] task2---<NSThread: 0x7fd910c05150>{number = 1, name = main}
    81 2016-03-20 00:47:53.273 同步串行[2248:284920] task3---<NSThread: 0x7fd910c05150>{number = 1, name = main}
    View Code
    2.2.2【代码】异步 + 串行
    • 串行队列中的任务都是按顺序执行,谁在前就先执行谁
    • 主线程和子线程平等,一样谁在前选执行谁
    • 执行完一个才会执行下一个任务
    串行队列,异步任务,在多线程中,是斯坦福大学最推荐的一种多线程方式!
    • 优点:将任务放在其他线程中工作,每个任务顺序执行,便于调试
    • 缺点:并发能力不强,最多只能使用一条线程!
     
     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 
     5 @end
     6 
     7 @implementation ViewController
     8 
     9 - (void)viewDidLoad {
    10   [super viewDidLoad];
    11   // Do any additional setup after loading the view, typically from a nib.
    12 }
    13 
    14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    15   [self asyncSerial];
    16 }
    17 
    18 #pragma mark - 异步函数 + 串行队列:会开启新的线程,在子线程执行任务,任务是串行的(顺序执行),只开一条线程
    19 - (void)asyncSerial {
    20   NSLog(@"异步串行 ----- begin");
    21   NSLog(@"主线程  ----- %@", [NSThread mainThread]);
    22 
    23   // 1.创建串行队列
    24   //写法1:
    25   dispatch_queue_t queue = dispatch_queue_create("TD", DISPATCH_QUEUE_SERIAL);
    26   //写法2:
    27   dispatch_queue_t queue = dispatch_queue_create("TD", NULL);
    28 
    29   // 2.将任务加入队列
    30   dispatch_async(queue, ^{
    31     NSLog(@"1-----%@", [NSThread currentThread]);
    32   });
    33   dispatch_async(queue, ^{
    34     NSLog(@"2-----%@", [NSThread currentThread]);
    35   });
    36   dispatch_async(queue, ^{
    37     NSLog(@"3-----%@", [NSThread currentThread]);
    38   });
    39 
    40   NSLog(@"异步串行 ----- end");
    41 }
    42 
    43 #pragma mark - 写法2
    44 - (void)serialAsyncDemo {
    45   // 1.创建队列
    46   dispatch_queue_t serialQueue =
    47       dispatch_queue_create("TD", DISPATCH_QUEUE_SERIAL);
    48 
    49   // 2.创建任务
    50   void (^task1)() = ^() {
    51     NSLog(@"task1---%@", [NSThread currentThread]);
    52   };
    53 
    54   void (^task2)() = ^() {
    55     NSLog(@"task2---%@", [NSThread currentThread]);
    56   };
    57 
    58   void (^task3)() = ^() {
    59     NSLog(@"task3---%@", [NSThread currentThread]);
    60   };
    61 
    62   // 3.将异步任务添加到串行队列
    63   dispatch_async(serialQueue, task1);
    64   dispatch_async(serialQueue, task2);
    65   dispatch_async(serialQueue, task3);
    66 }
    67 
    68 - (void)didReceiveMemoryWarning {
    69   [super didReceiveMemoryWarning];
    70   // Dispose of any resources that can be recreated.
    71 }
    72 
    73 @end
    74 
    75 打印结果:
    76 
    77 2016-03-20 00:59:38.392 异步串行[2486:297960] 异步串行 ----- begin
    78 2016-03-20 00:59:38.393 异步串行[2486:297960] 主线程  ----- <NSThread: 0x7ff271701ba0>{number = 1, name = main}
    79 2016-03-20 00:59:38.393 异步串行[2486:297960] 异步串行 ----- end
    80 2016-03-20 00:59:38.393 异步串行[2486:298011] 1-----<NSThread: 0x7ff2717abb30>{number = 2, name = (null)}
    81 2016-03-20 00:59:38.394 异步串行[2486:298011] 2-----<NSThread: 0x7ff2717abb30>{number = 2, name = (null)}
    82 2016-03-20 00:59:38.394 异步串行[2486:298011] 3-----<NSThread: 0x7ff2717abb30>{number = 2, name = (null)}
    83 
    84 2016-03-20 01:02:21.844 异步串行[2529:301017] task1---<NSThread: 0x7fddb9405f40>{number = 2, name = (null)}
    85 2016-03-20 01:02:21.845 异步串行[2529:301017] task2---<NSThread: 0x7fddb9405f40>{number = 2, name = (null)}
    86 2016-03-20 01:02:21.845 异步串行[2529:301017] task3---<NSThread: 0x7fddb9405f40>{number = 2, name = (null)}
    View Code

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

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

    2.2.3【代码】同步 + 主队列

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

     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 
     5 @end
     6 
     7 @implementation ViewController
     8 
     9 - (void)viewDidLoad {
    10   [super viewDidLoad];
    11   // Do any additional setup after loading the view, typically from a nib.
    12 }
    13 
    14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    15   [self syncMain];
    16 }
    17 
    18 #pragma mark - 同步函数 + 主队列:不会开启新的线程,会出现"死等",可能导致`主线程`卡死
    19 - (void)syncMain {
    20   NSLog(@"同步主队列 ----- begin");
    21 
    22   // 1.获得主队列
    23   dispatch_queue_t queue = dispatch_get_main_queue();
    24 
    25   // 2.将任务加入队列
    26   dispatch_sync(queue, ^{
    27     NSLog(@"1-----%@", [NSThread currentThread]);
    28   });
    29   dispatch_sync(queue, ^{
    30     NSLog(@"2-----%@", [NSThread currentThread]);
    31   });
    32   dispatch_sync(queue, ^{
    33     NSLog(@"3-----%@", [NSThread currentThread]);
    34   });
    35 
    36   NSLog(@"同步主队列 ----- end");
    37 }
    38 
    39 - (void)mainQueueSync {
    40   NSLog(@"同步主队列 ----- begin");
    41 
    42   // 1.获取主队列
    43   dispatch_queue_t mainQueue = dispatch_get_main_queue();
    44 
    45   // 2.创建队列
    46   void (^task1)() = ^() {
    47     NSLog(@"---task1---%@", [NSThread currentThread]);
    48   };
    49 
    50   void (^task2)() = ^() {
    51     NSLog(@"---task2---%@", [NSThread currentThread]);
    52   };
    53 
    54   void (^task3)() = ^() {
    55     NSLog(@"---task3---%@", [NSThread currentThread]);
    56   };
    57 
    58   // 3.将同步任务添加到并发队列中
    59   dispatch_sync(mainQueue, task1);
    60   dispatch_sync(mainQueue, task2);
    61   dispatch_sync(mainQueue, task3);
    62 
    63   NSLog(@"同步主队列 ----- end");
    64 }
    65 
    66 - (void)didReceiveMemoryWarning {
    67   [super didReceiveMemoryWarning];
    68   // Dispose of any resources that can be recreated.
    69 }
    70 
    71 @end
    72 
    73 打印结果:
    74 
    75 2016-03-20 01:23:55.594 同步主队列[3286:329220] 同步主队列 ----- begin
    View Code

     

    2.3【代码】异步 + 主队列

     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 
     5 @end
     6 
     7 @implementation ViewController
     8 
     9 - (void)viewDidLoad {
    10   [super viewDidLoad];
    11   // Do any additional setup after loading the view, typically from a nib.
    12 }
    13 
    14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    15   [self asyncMain];
    16 }
    17 
    18 #pragma mark -  异步函数 + 主队列:不会开启新的线程,在当前线程执行任务(主线程),任务是串行的(顺序执行),只开一条线程(适合处理 UI 或者是 UI事件)
    19 - (void)asyncMain {
    20   NSLog(@"异步主队列 ----- begin");
    21 
    22   // 1.获得主队列
    23   dispatch_queue_t queue = dispatch_get_main_queue();
    24 
    25   // 2.将任务加入队列
    26   dispatch_async(queue, ^{
    27     NSLog(@"1-----%@", [NSThread currentThread]);
    28   });
    29   dispatch_async(queue, ^{
    30     NSLog(@"2-----%@", [NSThread currentThread]);
    31   });
    32   dispatch_async(queue, ^{
    33     NSLog(@"3-----%@", [NSThread currentThread]);
    34   });
    35 
    36   NSLog(@"异步主队列 ----- end");
    37 }
    38 
    39 #pragma mark - 写法2
    40 - (void)mainQueueAsync {
    41   NSLog(@"异步主队列 ----- begin");
    42 
    43   // 1.获取主队列
    44   dispatch_queue_t mainQueue = dispatch_get_main_queue();
    45 
    46   // 2.创建任务
    47   void (^task1)() = ^() {
    48     NSLog(@"---async task1---%@", [NSThread currentThread]);
    49   };
    50 
    51   void (^task2)() = ^() {
    52     NSLog(@"---async task2---%@", [NSThread currentThread]);
    53   };
    54 
    55   void (^task3)() = ^() {
    56     NSLog(@"---async task3---%@", [NSThread currentThread]);
    57   };
    58 
    59   // 3.将异步任务添加到主队列中
    60   dispatch_async(mainQueue, task1);
    61   dispatch_async(mainQueue, task2);
    62   dispatch_async(mainQueue, task3);
    63 
    64   NSLog(@"异步主队列 ----- end");
    65 }
    66 
    67 - (void)didReceiveMemoryWarning {
    68   [super didReceiveMemoryWarning];
    69   // Dispose of any resources that can be recreated.
    70 }
    71 
    72 @end
    73 
    74 打印结果:
    75 
    76 2016-03-20 01:15:47.663 异步主队列[2949:318506] 异步主队列 ----- begin
    77 2016-03-20 01:15:47.663 异步主队列[2949:318506] 异步主队列 ----- end
    78 2016-03-20 01:15:47.664 异步主队列[2949:318506] 1-----<NSThread: 0x7feec9c082e0>{number = 1, name = main}
    79 2016-03-20 01:15:47.664 异步主队列[2949:318506] 2-----<NSThread: 0x7feec9c082e0>{number = 1, name = main}
    80 2016-03-20 01:15:47.664 异步主队列[2949:318506] 3-----<NSThread: 0x7feec9c082e0>{number = 1, name = main}
    81 
    82 2016-03-20 01:16:15.690 异步主队列[2970:319219] 异步主队列 ----- begin
    83 2016-03-20 01:16:15.691 异步主队列[2970:319219] 异步主队列 ----- end
    84 2016-03-20 01:16:15.691 异步主队列[2970:319219] ---async task1---<NSThread: 0x7f9de1c074e0>{number = 1, name = main}
    85 2016-03-20 01:16:15.691 异步主队列[2970:319219] ---async task2---<NSThread: 0x7f9de1c074e0>{number = 1, name = main}
    86 2016-03-20 01:16:15.692 异步主队列[2970:319219] ---async task3---<NSThread: 0x7f9de1c074e0>{number = 1, name = main}
    View Code

    2.2.4 总结

    GCD 队列类型的创建方式:

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

    注意:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列(比如同步主队列) 
     
    同步函数:无论是什么队列都不会开启线程
    (1)并发队列:不会开线程
    (2)串行队列:不会开线程
     
    异步函数:具备开启线程的能力(但不一定会开线程 ),开启几条线程由队列决定
    (1)并发队列:能开启N条线程
    (2)串行队列:开启1条线程
     

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

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


     
    3.0 线程间的通信
     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 @property(weak, nonatomic) IBOutlet UIImageView *imageView;
     5 @end
     6 
     7 @implementation ViewController
     8 
     9 - (void)viewDidLoad {
    10   [super viewDidLoad];
    11   // Do any additional setup after loading the view, typically from a nib.
    12 }
    13 
    14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    15 
    16   // 全局的异步并发
    17   dispatch_async(
    18       dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    19 
    20         // 图片的网络路径
    21         NSURL *url = [NSURL
    22             URLWithString:@"http://www.5068.com/u/faceimg/20140804114111.jpg"];
    23 
    24         // 加载图片
    25         NSData *data = [NSData dataWithContentsOfURL:url];
    26 
    27         // 生成图片
    28         UIImage *image = [UIImage imageWithData:data];
    29 
    30         // 回到主线程,执行 UI 刷新操作
    31         dispatch_async(dispatch_get_main_queue(), ^{
    32           self.imageView.image = image;
    33         });
    34       });
    35 }
    36 
    37 - (void)didReceiveMemoryWarning {
    38   [super didReceiveMemoryWarning];
    39   // Dispose of any resources that can be recreated.
    40 }
    41 
    42 @end
    图片下载示例

    4.1 其他用法 - barrier函数

     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 @end
     5 
     6 @implementation ViewController
     7 
     8 - (void)viewDidLoad {
     9   [super viewDidLoad];
    10   // Do any additional setup after loading the view, typically from a nib.
    11 }
    12 
    13 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    14 
    15   // 这里使用全局并发队列的方式会导致 dispatch_barrier_async 功能失效
    16   dispatch_queue_t queue =
    17       dispatch_queue_create("TD", DISPATCH_QUEUE_CONCURRENT);
    18 
    19   dispatch_async(queue, ^{
    20     NSLog(@"----1-----%@", [NSThread currentThread]);
    21   });
    22   dispatch_async(queue, ^{
    23     NSLog(@"----2-----%@", [NSThread currentThread]);
    24   });
    25 
    26   dispatch_barrier_async(queue, ^{
    27     NSLog(@"----barrier-----%@", [NSThread currentThread]);
    28   });
    29 
    30   dispatch_async(queue, ^{
    31     NSLog(@"----3-----%@", [NSThread currentThread]);
    32   });
    33   dispatch_async(queue, ^{
    34     NSLog(@"----4-----%@", [NSThread currentThread]);
    35   });
    36 }
    37 
    38 - (void)didReceiveMemoryWarning {
    39   [super didReceiveMemoryWarning];
    40   // Dispose of any resources that can be recreated.
    41 }
    42 
    43 @end
    44 
    45 打印结果:
    46 
    47 2016-03-21 15:42:16.214 其他用法 - barrier函数[1419:94670] ----2-----<NSThread: 0x7fc4a0c2c5d0>{number = 3, name = (null)}
    48 2016-03-21 15:42:16.214 其他用法 - barrier函数[1419:94732] ----1-----<NSThread: 0x7fc4a0f0cb20>{number = 2, name = (null)}
    49 2016-03-21 15:42:16.214 其他用法 - barrier函数[1419:94732] ----barrier-----<NSThread: 0x7fc4a0f0cb20>{number = 2, name = (null)}
    50 2016-03-21 15:42:16.215 其他用法 - barrier函数[1419:94732] ----3-----<NSThread: 0x7fc4a0f0cb20>{number = 2, name = (null)}
    51 2016-03-21 15:42:16.215 其他用法 - barrier函数[1419:94670] ----4-----<NSThread: 0x7fc4a0c2c5d0>{number = 3, name = (null)}
    View Code

    4.2 其他用法 - 延迟执行

     1 方法1:调用NSObject的方法
     2 
     3 // 该方法在那个线程调用,那么run就在哪个线程执行(当前线程),通常是主线程
     4 // 2秒后再调用self的run方法
     5 [selfperformSelector:@selector(run) withObject:nilafterDelay:2.0];
     6 
     7 方法2:使用GCD函数
     8 
     9 // 这里是在主线程执行,如果想要在子线程执行,选择相应的队列
    10 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    11         // 2秒后异步执行这里的代码...
    12     });
    13 
    14 方法3:使用NSTimer
    15 
    16 [NSTimer scheduledTimerWithTimeInterval:2.0
    17                                  target:self
    18                                selector:@selector(run)
    19                                userInfo:nil
    20                                 repeats:NO];
     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 
     5 @end
     6 
     7 @implementation ViewController
     8 
     9 - (void)viewDidLoad {
    10   [super viewDidLoad];
    11   // Do any additional setup after loading the view, typically from a nib.
    12 }
    13 
    14 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    15 
    16   [self delay1];
    17 }
    18 
    19 #pragma mark - 方法1:调用NSObject的方法
    20 - (void)delay1 {
    21   NSLog(@"touchesBegan-----");
    22 
    23   // 2秒后再调用self的run方法
    24   [self performSelector:@selector(run) withObject:nil afterDelay:2.0];
    25 }
    26 
    27 #pragma mark - 方法2:使用 GCD 函数
    28 - (void)delay2 {
    29   NSLog(@"touchesBegan-----");
    30 
    31   // 这里是在主线程执行,如果想要在子线程执行,选择相应的队列
    32   dispatch_after(
    33       dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)),
    34       dispatch_get_main_queue(), ^{
    35         NSLog(@"run-----");
    36       });
    37 }
    38 
    39 #pragma mark - 方法3:使用NSTimer定时器
    40 - (void)delay3 {
    41   NSLog(@"touchesBegan-----");
    42 
    43   [NSTimer scheduledTimerWithTimeInterval:2.0
    44                                    target:self
    45                                  selector:@selector(run)
    46                                  userInfo:nil
    47                                   repeats:NO];
    48 }
    49 
    50 - (void)run {
    51   NSLog(@"run-----");
    52 }
    53 
    54 - (void)didReceiveMemoryWarning {
    55   [super didReceiveMemoryWarning];
    56   // Dispose of any resources that can be recreated.
    57 }
    58 
    59 @end
    60 
    61 打印结果:
    62 
    63 2016-03-21 16:01:56.384 其他用法 - 延迟执行[1651:114465] touchesBegan-----
    64 2016-03-21 16:01:58.385 其他用法 - 延迟执行[1651:114465] run-----
    View Code

    4.3 其他用法 - 一次性代码

    1 使用dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,适合做资源的加载
    2 
    3 static dispatch_once_t onceToken;
    4   dispatch_once(&onceToken, ^{
    5      // 只执行1次的代码(这里面默认是线程安全的)
    6   });
    7 }

    4.4 其他用法 - 快速迭代

    1 使用dispatch_apply函数能进行快速迭代遍历:
    2 
    3 dispatch_apply(<#size_t iterations#>, <#dispatch_queue_t queue#>, ^(size_t) {
    4     //  代码
    5 });
    6 
    7 dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index){
    8     // 执行10次代码,index顺序不确定
    9 }); 
     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 
     5 @end
     6 
     7 @implementation ViewController
     8 
     9 - (void)viewDidLoad {
    10   [super viewDidLoad];
    11   // Do any additional setup after loading the view, typically from a nib.
    12 }
    13 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    14   [self apply];
    15 }
    16 
    17 #pragma mark - 文件剪切方法1:快速迭代
    18 - (void)apply {
    19 
    20   dispatch_queue_t queue =
    21       dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    22 
    23   NSString *from = @"/Users/TD/Desktop/From";
    24   NSString *to = @"/Users/TD/Desktop/To";
    25 
    26   NSFileManager *mgr = [NSFileManager defaultManager];
    27   NSArray *subpaths = [mgr subpathsAtPath:from];
    28 
    29   dispatch_apply(subpaths.count, queue, ^(size_t index) {
    30 
    31     NSString *subpath = subpaths[index];
    32     NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
    33     NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
    34 
    35     // 剪切
    36     [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
    37 
    38     NSLog(@"%@---%@", [NSThread currentThread], subpath);
    39   });
    40 }
    41 
    42 #pragma mark - 文件剪切方法2:传统方式
    43 - (void)moveFile {
    44   NSString *from = @"/Users/TD/Desktop/From";
    45   NSString *to = @"/Users/TD/Desktop/To";
    46 
    47   NSFileManager *mgr = [NSFileManager defaultManager];
    48 
    49   //获取文件夹下的所有文件路径,包括子文件夹下的文件路径
    50   NSArray *subpaths = [mgr subpathsAtPath:from];
    51 
    52   for (NSString *subpath in subpaths) {
    53 
    54     //全路径
    55     NSString *fromFullpath = [from stringByAppendingPathComponent:subpath];
    56     NSString *toFullpath = [to stringByAppendingPathComponent:subpath];
    57 
    58     dispatch_async(
    59         dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    60           // 剪切
    61           [mgr moveItemAtPath:fromFullpath toPath:toFullpath error:nil];
    62         });
    63   }
    64 }
    65 
    66 - (void)didReceiveMemoryWarning {
    67   [super didReceiveMemoryWarning];
    68   // Dispose of any resources that can be recreated.
    69 }
    70 
    71 @end
    文件剪切示例

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

    有这么一种需求:
    首先:分别异步执行2个耗时的操作
    其次:等2个异步操作都执行完毕后,再回到主线程执行操作 
     
    解决办法:用队列组,也叫做调度组
     
     1 // 创建一个队列组
     2 dispatch_group_t group = dispatch_group_create();
     3 
     4 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     5    // 执行1个耗时的异步操作
     6 });
     7 
     8 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     9    // 执行1个耗时的异步操作
    10 });
    11 
    12 dispatch_group_notify(group, dispatch_get_main_queue(),
    13 ^{
    14    // 等前面的异步操作都执行完毕后,回到主线程...
    15 });
     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 @property(weak, nonatomic) IBOutlet UIImageView *imageView;
     5 @property(nonatomic, strong) UIImage *image1; //图片1
     6 @property(nonatomic, strong) UIImage *image2; //图片2
     7 @end
     8 
     9 @implementation ViewController
    10 
    11 - (void)viewDidLoad {
    12   [super viewDidLoad];
    13   // Do any additional setup after loading the view, typically from a nib.
    14 }
    15 
    16 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    17 
    18   dispatch_queue_t queue =
    19       dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    20 
    21   // 创建一个队列组
    22   dispatch_group_t group = dispatch_group_create();
    23 
    24   // 1.下载图片1
    25   dispatch_group_async(group, queue, ^{
    26     // 图片的网络路径
    27     NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
    28     // 加载图片
    29     NSData *data = [NSData dataWithContentsOfURL:url];
    30     // 生成图片
    31     self.image1 = [UIImage imageWithData:data];
    32   });
    33 
    34   // 2.下载图片2
    35   dispatch_group_async(group, queue, ^{
    36     // 图片的网络路径
    37     NSURL *url = [NSURL URLWithString: @"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
    38     // 加载图片
    39     NSData *data = [NSData dataWithContentsOfURL:url];
    40     // 生成图片
    41     self.image2 = [UIImage imageWithData:data];
    42   });
    43 
    44   // 3.将图片1、图片2合成一张新的图片(也可以直接在此处回到主线程,只不过是因为绘制图片比较耗时,没有放在主线程而已)
    45   dispatch_group_notify(group, queue, ^{
    46 
    47     // 开启新的图形上下文
    48     UIGraphicsBeginImageContext(CGSizeMake(100, 100));
    49 
    50     // 绘制图片
    51     [self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
    52     [self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];
    53 
    54     // 取得上下文中的图片
    55     UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    56 
    57     // 结束上下文
    58     UIGraphicsEndImageContext();
    59 
    60     // 回到主线程显示图片
    61     dispatch_async(dispatch_get_main_queue(), ^{
    62       // 4.将新图片显示出来
    63       self.imageView.image = image;
    64     });
    65   });
    66 }
    67 
    68 - (void)didReceiveMemoryWarning {
    69   [super didReceiveMemoryWarning];
    70   // Dispose of any resources that can be recreated.
    71 }
    72 
    73 @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的定时器的

     1 #import "ViewController.h"
     2 
     3 @interface ViewController ()
     4 // 定时器 (这里不用带*,因为dispatch_source_t就是个类,内部已经包含了*)
     5 @property(nonatomic, strong) dispatch_source_t timer;
     6 @end
     7 
     8 @implementation ViewController
     9 
    10 - (void)viewDidLoad {
    11   [super viewDidLoad];
    12 }
    13 
    14 int count = 0;
    15 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    16 
    17   // 1、获得队列
    18   //    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    19   dispatch_queue_t queue = dispatch_get_main_queue();
    20 
    21   // 2、创建一个定时器 (dispatch_source_t本质还是个OC对象)
    22   self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    23 
    24   // 3、设置定时器的各种属性(几时开始任务,每隔多长时间执行一次)
    25 
    26   // 触发时间(何时开始执行第一个任务)
    27   // 比当前时间晚1秒
    28   dispatch_time_t start =
    29       dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
    30   // 马上就执行
    31   //  dispatch_time_t start1 = DISPATCH_TIME_NOW;
    32 
    33   // 时间间隔。GCD的时间参数,一般是纳秒(1秒 == 10的9次方纳秒)
    34   uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
    35 
    36   // 参数:(1)定时器名字 (2)触发时间 (3)时间间隔 (4)0
    37   dispatch_source_set_timer(self.timer, start, interval, 0);
    38 
    39   // 4、设置定时器的回调
    40   dispatch_source_set_event_handler(self.timer, ^{
    41     NSLog(@"------------%@", [NSThread currentThread]);
    42 
    43     count++;
    44 
    45     if (count == 4) {
    46       // 取消定时器
    47       dispatch_cancel(self.timer);
    48       self.timer = nil;
    49     }
    50   });
    51 
    52   // 5、启动定时器
    53   dispatch_resume(self.timer);
    54 }
    55 @end
    56 
    57 打印结果:
    58 
    59 2016-03-24 01:12:39.066 04-掌握-GCD定时器[1179:76865] ------------<NSThread: 0x7f8af0705770>{number = 1, name = main}
    60 2016-03-24 01:12:40.067 04-掌握-GCD定时器[1179:76865] ------------<NSThread: 0x7f8af0705770>{number = 1, name = main}
    61 2016-03-24 01:12:41.066 04-掌握-GCD定时器[1179:76865] ------------<NSThread: 0x7f8af0705770>{number = 1, name = main}
    62 2016-03-24 01:12:42.067 04-掌握-GCD定时器[1179:76865] ------------<NSThread: 0x7f8af0705770>{number = 1, name = main}
    View Code

      


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

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

     

     
     
  • 相关阅读:
    python中的if...else...、while、for
    linux的/etc/passwd、/etc/shadow、/etc/group和/etc/gshadow
    [国家集训队]middle
    [SCOI2007]修车
    基本图论-连通分量(强/弱联通 割点/边 边/点双)
    [NOI2008]奥运物流
    [NOI2008]假面舞会
    [NOI2008]设计路线
    [SCOI2009]windy数
    [SCOI2013]多项式的运算
  • 原文地址:https://www.cnblogs.com/shorfng/p/5322525.html
Copyright © 2011-2022 走看看