《Objective-C 高级编程》干货三部曲(三):GCD篇

本篇是三部曲的最后一篇,讲解了本书的第三章的内容。在这一章里,做者主要介绍了GCD技术,它是基于C语言的API,开发者只须要将任务放在block内,并指定好追加的队列,就能够完成多线程开发。git

可是多线程开发时容易发生的一些问题:程序员

  • 多个线程更新相同的资源:数据竞争。
  • 多个线程相互持续等待:死锁。
  • 使用太多的线程致使消耗内存。

虽然解决这些问题的代价是会使程序的复杂度上升,可是多线程技术仍然是必须使用的:由于使用多线程编程能够保证应用程序的响应性能。若是耗时操做阻塞了主线程的RunLoop,会致使用户界面没法响应用户的操做,因此必须开启子线程将耗时操做放在子线程中处理。那么咱们应该怎么进行多线程开发呢?在讲解以前先看一下本文结构(GCD部分):github

《Objective-C高级编程》 干货三部曲

本文的Demo地址:knightsj/iOS_Demo/gcd_demo 虽然文章里应给出了详细的输出结果,但仍是但愿读者能够将demo下载后仔细对照一下代码并体会。数据库

队列

Dispatch Queue是执行处理的等待队列,按照任务(block)追加到队列里的顺序,先进先出执行处理。编程

而等待队列有两种数组

  • Serial Dispatch Queue:串行队列,等待当前执行任务处理结束的队列。
  • Concurrent Dispatch Queue:并发队列,不等待当前执行任务处理结束的队列。

串行队列

将任务追加到串行队列:安全

- (void)serialQueue
{
    dispatch_queue_t queue = dispatch_queue_create("serial queue", NULL);
    for (NSInteger index = 0; index < 6; index ++) {
        dispatch_async(queue, ^{
            NSLog(@"task index %ld in serial queue",index);
        });
    }
}
复制代码

输出:多线程

gcd_demo[33484:2481120] task index 0 in serial queue
gcd_demo[33484:2481120] task index 1 in serial queue
gcd_demo[33484:2481120] task index 2 in serial queue
gcd_demo[33484:2481120] task index 3 in serial queue
gcd_demo[33484:2481120] task index 4 in serial queue
gcd_demo[33484:2481120] task index 5 in serial queue
复制代码

经过dispatch_queue_create函数能够建立队列,第一个函数为队列的名称,第二个参数是NULLDISPATCH_QUEUE_SERIAL时,返回的队列就是串行队列。并发

为了不重复代码,我在这里使用了for循环,将任务追加到了queue中。app

注意,这里的任务是按照顺序执行的。说明任务是以阻塞的形式执行的:必须等待上一个任务执行完成才能执行如今的任务。也就是说:一个Serial Dispatch Queue中同时只能执行一个追加处理(任务block),并且系统对于一个Serial Dispatch Queue只生成并使用一个线程。

可是,若是咱们将6个任务分别追加到6个Serial Dispatch Queue中,那么系统就会同时处理这6个任务(由于会另开启6个子线程):

- (void)multiSerialQueue
{
    for (NSInteger index = 0; index < 10; index ++) {
        //新建一个serial queue
        dispatch_queue_t queue = dispatch_queue_create("different serial queue", NULL);
        dispatch_async(queue, ^{
            NSLog(@"serial queue index : %ld",index);
        });
    }
}
复制代码

输出结果:

gcd_demo[33576:2485282] serial queue index : 1
gcd_demo[33576:2485264] serial queue index : 0
gcd_demo[33576:2485267] serial queue index : 2
gcd_demo[33576:2485265] serial queue index : 3
gcd_demo[33576:2485291] serial queue index : 4
gcd_demo[33576:2485265] serial queue index : 5
复制代码

从输出结果能够看出来,这里的6个任务并非按顺序执行的。

须要注意的是:一旦开发者新建了一个串行队列,系统必定会开启一个子线程,因此在使用串行队列的时候,必定只建立真正须要建立的串行队列,避免资源浪费。

并发队列

将任务追加到并发队列:

- (void)concurrentQueue
{
    dispatch_queue_t queue = dispatch_queue_create("concurrent queue", DISPATCH_QUEUE_CONCURRENT);
    for (NSInteger index = 0; index < 6; index ++) {
        dispatch_async(queue, ^{
            NSLog(@"task index %ld in concurrent queue",index);
        });
    }
}
复制代码

输出结果:

gcd_demo[33550:2484160] task index 1 in concurrent queue
gcd_demo[33550:2484159] task index 0 in concurrent queue
gcd_demo[33550:2484162] task index 2 in concurrent queue
gcd_demo[33550:2484182] task index 3 in concurrent queue
gcd_demo[33550:2484183] task index 4 in concurrent queue
gcd_demo[33550:2484160] task index 5 in concurrent queue
复制代码

能够看到,dispatch_queue_create函数的第二个参数是DISPATCH_QUEUE_CONCURRENT

注意,这里追加到并发队列的6个任务并非按照顺序执行的,符合上面并发队列的定义。

扩展知识:iOS和OSX基于Dispatch Queue中的处理数,CPU核数,以及CPU负荷等当前系统的状态来决定Concurrent Dispatch Queue中并发处理的任务数。

队列的命名

如今咱们知道dispatch_queue_create方法第一个参数指定了这个新建队列的名称,推荐使用逆序quan cheng全程域名(FQDN,fully qualified domain name)。这个名称能够在Xcode和CrashLog中显示出来,对bug的追踪颇有帮助。

在继续讲解以前作个小总结,如今咱们知道了:

  • 如何建立串行队列和并发队列。
  • 将任务追加到这两种队列里之后的执行效果。
  • 将任务追加到多个串行队列会使这几个任务在不一样的线程执行。

实际上,系统给咱们提供了两种特殊的队列,分别对应串行队列和并发队列:

系统提供的队列

Main Dispatch Queue

主队列:放在这个队列里的任务会追加到主线程的RunLoop中执行。须要刷新UI的时候咱们能够直接获取这个队列,将任务追加到这个队列中。

Globle Dispatch Queue

全局并发队列:开发者能够不须要特地经过dispatch_queue_create方法建立一个Concurrent Dispatch Queue,能够将任务直接放在这个全局并发队列里面。

有一个常见的例子能够充分体现两者的使用方法:

//获取全局并发队列进行耗时操做 
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

          //加载图片
          NSData *dataFromURL = [NSData dataWithContentsOfURL:imageURL];
          UIImage *imageFromData = [UIImage imageWithData:dataFromURL];

      dispatch_async(dispatch_get_main_queue(), ^{

              //获取主队列,在图片加载完成后更新UIImageView
              UIImageView *imageView = [[UIImageView alloc] initWithImage:imageFromData];          
      });      
  });
复制代码

GCD的各类函数

dispatch_set_target_queue

这个函数有两个做用:

  1. 改变队列的优先级。
  2. 防止多个串行队列的并发执行。

改变队列的优先级

dispatch_queue_create方法生成的串行队列合并发队列的优先级都是与默认优先级的Globle Dispatch Queue一致。

若是想要变动某个队列的优先级,须要使用dispatch_set_target_queue函数。 举个🌰:建立一个在后台执行动做处理的Serial Dispatch Queue

//需求:生成一个后台的串行队列
- (void)changePriority
{
    dispatch_queue_t queue = dispatch_queue_create("queue", NULL);
    dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    
    //第一个参数:须要改变优先级的队列;
    //第二个参数:目标队列
    dispatch_set_target_queue(queue, bgQueue);
}
复制代码

防止多个串行队列的并发执行

有时,咱们将不能并发执行的处理追加到多个Serial Dispatch Queue中时,可使用dispatch_set_target_queue函数将目标函数定为某个Serial Dispatch Queue,就能够防止这些处理的并发执行。

代码:

NSMutableArray *array = [NSMutableArray array];
for (NSInteger index = 0; index < 5; index ++) {
        //5个串行队列
        dispatch_queue_t serial_queue = dispatch_queue_create("serial_queue", NULL);
        [array addObject:serial_queue];
}
    
[array enumerateObjectsUsingBlock:^(dispatch_queue_t queue, NSUInteger idx, BOOL * _Nonnull stop) {
        
    dispatch_async(queue, ^{
        NSLog(@"任务%ld",idx);
    });
}];
复制代码

输出:

gcd_demo[40329:2999714] 任务1
gcd_demo[40329:2999726] 任务0
gcd_demo[40329:2999717] 任务2
gcd_demo[40329:2999715] 任务3
gcd_demo[40329:2999730] 任务4
复制代码

咱们能够看到,若是仅仅是将任务追加到5个串行队列中,那么这些任务就会并发执行。

那接下来看看使用dispatch_set_target_queue方法之后:

//多个串行队列,设置了target queue
NSMutableArray *array = [NSMutableArray array];
dispatch_queue_t serial_queue_target = dispatch_queue_create("queue_target", NULL);

for (NSInteger index = 0; index < 5; index ++) {
      
    //分别给每一个队列设置相同的target queue 
    dispatch_queue_t serial_queue = dispatch_queue_create("serial_queue", NULL);
    dispatch_set_target_queue(serial_queue, serial_queue_target);
    [array addObject:serial_queue];
}
    
[array enumerateObjectsUsingBlock:^(dispatch_queue_t queue, NSUInteger idx, BOOL * _Nonnull stop) {
        
    dispatch_async(queue, ^{
        NSLog(@"任务%ld",idx);
    });
}];
复制代码

输出:

gcd_demo[40408:3004382] 任务0
gcd_demo[40408:3004382] 任务1
gcd_demo[40408:3004382] 任务2
gcd_demo[40408:3004382] 任务3
gcd_demo[40408:3004382] 任务4
复制代码

很显然,这些任务就按顺序执行了。

dispatch_after

dispatch_after解决的问题:某个线程里,在指定的时间后处理某个任务:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"三秒以后追加到队列");
});
复制代码

注意:不是在3秒以后处理任务,准确来讲是3秒以后追加到队列。因此说,若是这个线程的runloop执行1/60秒一次,那么这个block最快会在3秒后执行,最慢会在(3+1/60)秒后执行。并且,若是这个队列自己还有延迟,那么这个block的延迟执行时间会更多。

dispatch_group

若是遇到这样到需求:所有处理完多个预处理任务(block_1 ~ 4)后执行某个任务(block_finish),咱们有两个方法:

  • 若是预处理任务须要一个接一个的执行:将全部须要先处理完的任务追加到Serial Dispatch Queue中,并在最后追加最后处理的任务(block_finish)。
  • 若是预处理任务须要并发执行:须要使用dispatch_group函数,将这些预处理的block追加到global dispatch queue中。

分别详细讲解一下两种需求的实现方式:

预处理任务须要一个接一个的执行:

这个需求的实现方式相对简单一点,只要将全部的任务(block_1 ~ 4 + block_finish)放在一个串行队列中便可,由于都是按照顺序执行的,只要不作多余的事情,这些任务就会乖乖地按顺序执行。

预处理任务须要一个接一个的执行:

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSInteger index = 0; index < 5; index ++) {
        dispatch_group_async(group, queue, ^{
            NSLog(@"任务%ld",index);
        });
}
    
dispatch_group_notify(group, queue, ^{
    NSLog(@"最后的任务");
});
复制代码

输出:

gcd_demo[40905:3057237] 任务0
gcd_demo[40905:3057235] 任务1
gcd_demo[40905:3057234] 任务2
gcd_demo[40905:3057253] 任务3
gcd_demo[40905:3057237] 任务4
gcd_demo[40905:3057237] 最后的任务
复制代码

由于这些预处理任务都是追加到global dispatch queue中的,因此这些任务的执行任务的顺序是不定的。可是最后的任务必定是最后输出的。

dispatch_group_notify函数监听传入的group中任务的完成,等这些任务所有执行之后,再将第三个参数(block)追加到第二个参数的queue(相同的queue)中。

dispatch_group_wait

dispatch_group_wait 也是配合dispatch_group 使用的,利用这个函数,咱们能够设定group内部全部任务执行完成的超时时间。

一共有两种状况:超时的状况和没有超时的状况:

超时的状况:

- (void)dispatch_wait_1
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (NSInteger index = 0; index < 5; index ++) {
        dispatch_group_async(group, queue, ^{
            for (NSInteger i = 0; i< 1000000000; i ++) {
                
            }
            NSLog(@"任务%ld",index);
        });
    }
    
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
    long result = dispatch_group_wait(group, time);
    if (result == 0) {
        
        NSLog(@"group内部的任务所有结束");
        
    }else{
        
        NSLog(@"虽然过了超时时间,group还有任务没有完成");
    }
    
}
复制代码

输出:

gcd_demo[41277:3087481] 虽然过了超时时间,group还有任务没有完成,结果是断定为超时
gcd_demo[41277:3087563] 任务0
gcd_demo[41277:3087564] 任务2
gcd_demo[41277:3087579] 任务3
gcd_demo[41277:3087566] 任务1
gcd_demo[41277:3087563] 任务4
复制代码

没有超时的状况:

- (void)dispatch_wait_2
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (NSInteger index = 0; index < 5; index ++) {
        dispatch_group_async(group, queue, ^{
            for (NSInteger i = 0; i< 100000000; i ++) {
                
            }
            NSLog(@"任务%ld",index);
        });
    }
    
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
    long result = dispatch_group_wait(group, time);
    if (result == 0) {
        
        NSLog(@"group内部的任务所有结束");
        
    }else{
        
        NSLog(@"虽然过了超时时间,group还有任务没有完成");
    }
    
}
复制代码

输出:

gcd_demo[41357:3092079] 任务2
gcd_demo[41357:3092076] 任务3
gcd_demo[41357:3092092] 任务1
gcd_demo[41357:3092077] 任务0
gcd_demo[41357:3092079] 任务4
gcd_demo[41357:3091956] group内部的任务所有结束,在超时的时间之内完成,结果断定为没有超时
复制代码

注意: 一旦调用dispatch_group_wait之后,当通过了函数中指定的超时时间后 或者 指定的group内的任务所有执行后会返回这个函数的结果:

  • 通过了函数中指定的超时时间后,group内部的任务没有所有完成,断定为超时,不然,没有超时
  • 指定的group内的任务所有执行后,通过的时间长于超时时间,断定为超时,不然,没有超时。

也就是说: 若是指定的超时时间为DISPATCH_TIME_NOW,那么则没有等待,当即判断group内的任务是否完成。

能够看出,指定的超时时间为DISPATCH_TIME_NOW的时候至关于dispatch_group_notify函数的使用:判断group内的任务是否都完成。

然而dispatch_group_notify函数是做者推荐的,由于经过这个函数能够直接设置最后任务所被追加的队列,使用起来相对比较方便。

dispatch_barrier_async

关于解决数据竞争的方法:读取处理是能够并发的,可是写入处理倒是不容许并发执行的。

因此合理的方案是这样的:

  • 读取处理追加到concurrent dispatch queue中
  • 写入处理在任何一个读取处理没有执行的状态下,追加到serial dispatch queue中(也就是说,在写入处理结束以前,读取处理不可执行)。

咱们看看如何使用dispatch_barrier_async来解决这个问题。

为了帮助你们理解,我构思了一个例子:

  1. 3名董事和总裁开会,在每一个人都查看完合同以后,由总裁签字。
  2. 总裁签字以后,全部人再审核一次合同。

这个需求有三个关键点:

  • 关键点1:全部与会人员查看和审核合同,是同时进行的,无序的行为。
  • 关键点2:只有与会人员都查看了合同以后,总裁才能签字。
  • 关键点3: 只有总裁签字以后,才能进行审核。

用代码看一下:

- (void)dispatch_barrier
{
    dispatch_queue_t meetingQueue = dispatch_queue_create("com.meeting.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(meetingQueue, ^{
        NSLog(@"总裁查看合同");
    });
    
    dispatch_async(meetingQueue, ^{
        NSLog(@"董事1查看合同");
    });
    
    dispatch_async(meetingQueue, ^{
        NSLog(@"董事2查看合同");
    });
    
    dispatch_async(meetingQueue, ^{
        NSLog(@"董事3查看合同");
    });
    
    dispatch_barrier_async(meetingQueue, ^{
        NSLog(@"总裁签字");
    });
    
    dispatch_async(meetingQueue, ^{
        NSLog(@"总裁审核合同");
    });
    
    dispatch_async(meetingQueue, ^{
        NSLog(@"董事1审核合同");
    });
    
    dispatch_async(meetingQueue, ^{
        NSLog(@"董事2审核合同");
    });
    
    dispatch_async(meetingQueue, ^{
        NSLog(@"董事3审核合同");
    });
}
复制代码

输出结果:

gcd_demo[41791:3140315] 总裁查看合同
gcd_demo[41791:3140296] 董事1查看合同
gcd_demo[41791:3140297] 董事3查看合同
gcd_demo[41791:3140299] 董事2查看合同
gcd_demo[41791:3140299] 总裁签字
gcd_demo[41791:3140299] 总裁审核合同
gcd_demo[41791:3140297] 董事1审核合同
gcd_demo[41791:3140296] 董事2审核合同
gcd_demo[41791:3140320] 董事3审核合同
复制代码

在这里,咱们能够将meetingQueue当作是会议的时间线。总裁签字这个行为至关于写操做,其余都至关于读操做。使用dispatch_barrier_async之后,以前的全部并发任务都会被dispatch_barrier_async里的任务拦截掉,就像函数名称里的“栅栏”同样。

所以,使用Concurrent Dispatch Queue 和 dispatch_barrier_async 函数能够实现高效率的数据库访问和文件访问。

dispatch_sync

到目前为止的全部例子都使用的是异步函数,有异步就必定会有同步,那么如今就来区分一下同步和异步函数的区别:

  • dispatch_async:异步函数,这个函数会当即返回,不作任何等待,它所指定的block“非同步地”追加到指定的队列中。
  • dispatch_sync:同步函数,这个函数不会当即返回,它会一直等待追加到特定队列中的制定block完成工做后才返回,因此它的目的(也是效果)是阻塞当前线程。

举个例子:

- (void)dispatch_sync_1
{
    //同步处理
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"同步处理开始");
    
    __block NSInteger num = 0;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_sync(queue, ^{
        //模仿耗时操做
        for (NSInteger i = 0; i< 1000000000; i ++) {
            num++;
        }
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"同步处理完毕");
    });
    NSLog(@"%ld",num);
    NSLog(@"%@",[NSThread currentThread]);
}
复制代码

输出结果:

gcd_demo[5604:188687] <NSThread: 0x60800006fa40>{number = 1, name = main}
gcd_demo[5604:188687] 同步处理开始
gcd_demo[5604:188687] <NSThread: 0x60800006fa40>{number = 1, name = main}
gcd_demo[5604:188687] 同步处理完毕
gcd_demo[5604:188687] 1000000000
gcd_demo[5604:188687] <NSThread: 0x60800006fa40>{number = 1, name = main}
复制代码

在最开始的时候只打印前两行,循环完毕以后才打印后面的内容。 由于是同步函数,它阻塞了当前线程(主线程),因此只能等到block内部的任务都结束后,才能打印下面的两行。

可是若是使用异步函数会怎样呢?

- (void)dispatch_sync_2
{
    //异步处理
    NSLog(@"%@",[NSThread currentThread]);
    NSLog(@"异步处理开始");
    
    __block NSInteger num = 0;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        //模仿耗时操做
        for (NSInteger i = 0; i< 1000000000; i ++) {
            num++;
        }
        NSLog(@"%@",[NSThread currentThread]);
        NSLog(@"异步处理完毕");
    });
    NSLog(@"%ld",num);
    NSLog(@"%@",[NSThread currentThread]);
}
复制代码

输出:

gcd_demo[5685:194233] <NSThread: 0x600000071f00>{number = 1, name = main}
gcd_demo[5685:194233] 异步处理开始
gcd_demo[5685:194233] 0
gcd_demo[5685:194233] <NSThread: 0x600000071f00>{number = 1, name = main}
gcd_demo[5685:194280] <NSThread: 0x608000260400>{number = 3, name = (null)}
gcd_demo[5685:194280] 异步处理完毕
复制代码

咱们能够看到,不一样于上面的状况,block下面的两个输出是先打印的(由于没有通过for循环的计算,num的值是0)。由于是异步处理,因此没有等待block中任务的完成就当即返回了。

了解了同步异步的区别以后,咱们看一下使用同步函数容易发生的问题:若是给同步函数传入的队列是串行队列的时候就会容易形成死锁。看一下一个死锁的例子:

- (void)dispatch_sync_3
{
    NSLog(@"任务1");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        
        NSLog(@"任务2");
    });
    
    NSLog(@"任务3");
}
复制代码

上面的代码只能输出任务1,并造成死锁。 由于任务2被追加到了主队列的最后,因此它须要等待任务3执行完成。 但又由于是同步函数,任务3也在等待任务2执行完成。 两者互相等待,因此造成了死锁。

dispatch_apply

经过dispatch_apply函数,咱们能够按照指定的次数将block追加到指定的队列中。并等待所有处理执行结束。

- (void)dispatch_apply_1
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%ld",index);
    });
    NSLog(@"完毕");
}
复制代码
gcd_demo[6128:240332] 1
gcd_demo[6128:240331] 0
gcd_demo[6128:240334] 2
gcd_demo[6128:240332] 4
gcd_demo[6128:240334] 6
gcd_demo[6128:240331] 5
gcd_demo[6128:240332] 7
gcd_demo[6128:240334] 8
gcd_demo[6128:240331] 9
gcd_demo[6128:240259] 3
gcd_demo[6128:240259] 完毕
复制代码

咱们也能够用这个函数来遍历数组,取得下标进行操做:

- (void)dispatch_apply_2
{
    NSArray *array = @[@1,@10,@43,@13,@33];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply([array count], queue, ^(size_t index) {
        NSLog(@"%@",array[index]);
    });
    NSLog(@"完毕");
}
复制代码

输出:

gcd_demo[6180:244316] 10
gcd_demo[6180:244313] 1
gcd_demo[6180:244316] 33
gcd_demo[6180:244314] 43
gcd_demo[6180:244261] 13
gcd_demo[6180:244261] 完毕
复制代码

咱们能够看到dispatch_apply函数与dispatch_sync函数一样具备阻塞的做用(dispatch_apply函数返回后才打印完毕)。

咱们也能够在dispatch_async函数里执行dispatch_apply函数:

- (void)dispatch_apply_3
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        
        NSArray *array = @[@1,@10,@43,@13,@33];
        __block  NSInteger sum = 0;
    
        dispatch_apply([array count], queue, ^(size_t index) {
            NSNumber *number = array[index];
            NSInteger num = [number integerValue];
            sum += num;
        });
        
        dispatch_async(dispatch_get_main_queue(), ^{
            //回到主线程,拿到总和
            NSLog(@"完毕");
            NSLog(@"%ld",sum);
        });
    });
}
复制代码

dispatch_suspend/dispatch_resume

挂起函数调用后对已经执行的处理没有影响,可是追加到队列中可是还没有执行的处理会在此以后中止执行。

dispatch_suspend(queue);
dispatch_resume(queue);
复制代码

dispatch_once

经过dispatch_once处理的代码只执行一次,并且是线程安全的:

- (void)dispatch_once_1
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (NSInteger index = 0; index < 5; index++) {
        
        dispatch_async(queue, ^{
            [self onceCode];
        });
    }
}


- (void)onceCode
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"只执行一次的代码");
    });
}
复制代码

输出:

gcd_demo[7556:361196] 只执行一次的代码
复制代码

该函数主要用于单例模式的使用。

到这里终于总结完啦,这本书加深了我对iOS内存管理,block以及GCD的理解,但愿我写的这三篇能对您有所帮助~


本文已经同步到我的博客:传送门

---------------------------- 2018年7月17日更新 ----------------------------

注意注意!!!

笔者在近期开通了我的公众号,主要分享编程,读书笔记,思考类的文章。

  • 编程类文章:包括笔者之前发布的精选技术文章,以及后续发布的技术文章(以原创为主),而且逐渐脱离 iOS 的内容,将侧重点会转移到提升编程能力的方向上。
  • 读书笔记类文章:分享编程类思考类心理类职场类书籍的读书笔记。
  • 思考类文章:分享笔者平时在技术上生活上的思考。

由于公众号天天发布的消息数有限制,因此到目前为止尚未将全部过去的精选文章都发布在公众号上,后续会逐步发布的。

并且由于各大博客平台的各类限制,后面还会在公众号上发布一些短小精干,以小见大的干货文章哦~

扫下方的公众号二维码并点击关注,期待与您的共同成长~

公众号:程序员维他命
相关文章
相关标签/搜索