iOS 一篇文章搞定GCD

想了解NSOperation与GCD的区别可参考iOS多线程之NSOperation及简单练习


文章内容较长,介绍下主要的目录
一、GCD介绍
二、任务的执行:同步、异步与栅栏
三、队列
四、代码使用示例
五、一次性执行及单例创建
六、延迟操作
七、栅栏调度和线程通信示例
八、调度组(dispatch_group)
九、快速迭代(类似for循环)
十、信号量(dispatch_semaphore)
其他的CGD相关内容我会慢慢补充的~

一、GCD介绍

1.简介

Grand Central Dispatch,既大名鼎鼎的狗艹的.它是苹果为多核的并行运算提出的解决方案,会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是会自动管理线程的生命周期(创建线程、调度任务、销毁线程),只需要告诉GCD要执行什么任务,不需要编写任何管理代码。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便.

2.GCD的核心概念

2.1 GCD中的主要核心概念是任务与队列

  • 任务:执行什么操作

  • 队列:用来存放任务

2.2 简要执行流程和说明:

  • 1 将任务添加到队列,并且指定执行任务的函数

  • 2 任务使用 block 封装
    任务的 block 没有参数也没有返回值

  • 3 执行任务的函数

  • 异步 dispatch_async

    • 不用等待当前语句执行完毕,就可以执行下一条语句

    • 会开启线程执行 block 的任务

    • 异步是多线程的代名词

  • 同步 dispatch_sync

  • 必须等待当前语句执行完毕,才会执行下一条语句

  • 不会开启线程

  • 在当前执行 block 的任务

  • 4 队列 - 负责调度任务

  • 串行队列

  • 并发队列

  • 主队列

  • 全局队列

3.GCD与NSThread的对比

  • 所有的代码写在一起的,让代码更加简单,易于阅读和维护

    NSThread 通过 @selector 指定要执行的方法,代码分散。

    GCD 通过 block 指定要执行的代码,代码集中。

  • 使用 GCD 不需要管理线程的创建/销毁/复用的过程!程序员不用关心线程的生命周期。

  • 如果要开多个线程 NSThread 必须实例化多个线程对象。

  • NSThread 靠 NSObject 的分类方法实现的线程间通讯,GCD 靠 block。

二、任务的执行:同步、异步与栅栏

同步(dispatch_sync):任务执行时,只能在当前线程执行任务,不具备开启其他线程的能力


dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

异步 (dispatch_async):具备开启新线程的能力,能将任务放在新线程中执行


dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

栅栏 (dispatch_barrier_async):

功能:拦截前面的任务,等前面任务全部执行完毕后才会执行栅栏的任务,待栅栏任务执行完毕后才会继续执行后面的任务

使用方法:

使用栅栏,就不就能使用全局队列(苹果对全局队列内部进行了修改)且所有的任务都必须添加到同一队列中,栅栏才能起到拦截作用

详情请看七的示例代码


dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block)

iOS 一篇文章搞定GCD_第1张图片
barrier执行示意图.png

三、队列

1.串行队列

  • 特点:

  • 以先进先出的方式,顺序调度队列中的任务执行

  • 无论队列中指定执行任务的方式是同步还是异步,都只会等待前一个任务执行完成后再被调度

  • 创建方式:


dispatch_queue_t queue = dispatch_queue_create("自定义线程名", DISPATCH_QUEUE_SERIAL);

dispatch_queue_t queue = dispatch_queue_create("自定义线程名", NULL);
  • 注意:

当使用同步执行任务的方式(sync)往串行队列中添加任务,会卡主当前的串行队列,造成死锁

2.并发队列

  • 特点:

  • 以随机的方式并发调度队列中的任务执行

  • 若是以同步的方式执行任务,会等待任务执行完成后,再调度队列中的其他任务(既在同步的方式下,并发功能不会生效)

  • 若是以异步的方式执行任务,只要底层线程池中有可用的资源,就会直接执行队列中的其他任务

  • 创建方式:


dispatch_queue_t queue = dispatch_queue_create("自定义线程名", DISPATCH_QUEUE_CONCURRENT);

创建串行和并发队列的函数参数:

第一个参数: 队列的名称

第二个参数: 告诉系统需要创建一个并发队列还是串行队列

-DISPATCH_QUEUE_SERIAL 串行

-DISPATCH_QUEUE_CONCURRENT 并发

3.主队列

  • 特点:

  • 专用用在主线程上调度任务的队列

  • 是一种系统提供的特殊串行队列

  • 如果当前主线程正在执行任务,那么主队列中所添加的任务就不会被调度

  • 主队列只需要获取,不用手动创建

  • 获取方式:


dispatch_queue_t queue = dispatch_get_main_queue();

4.全局队列

  • 特点:

  • 是系统提供的一种并发队列,以供全局使用

  • 无需创建,可直接获取

  • 若要使用栅栏阻塞任务,就不能使用全局队列,应自己创建并发队列

  • 获取方式:


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  • 全局队列参数:

/* 第一个参数:服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级

iOS 8.0(新增)
  ○ QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
  ○ QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
  ○ QOS_CLASS_DEFAULT 0x15, 默认(用来底层重置队列使用的,不是给程序员用的)
  ○ QOS_CLASS_UTILITY 0x11, 实用工具(专门用来处理耗时操作!)
  ○ QOS_CLASS_BACKGROUND 0x09, 后台
  ○ QOS_CLASS_UNSPECIFIED 0x00, 未指定,可以和iOS 7.0 适配

iOS 7.0
  ○ DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级
  ○ DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级
  ○ DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级
  ○ DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台优先级 为未来保留使用的,应该永远传入0

第二个参数:无用,传入0既可

结论:如果要适配 iOS 7.0 & 8.0,使用以下代码: dispatch_get_global_queue(0, 0);

*/

四、代码使用示例


#import "ViewController.h"

@implementation ViewController
/*
如果是在子线程中调用 同步函数 + 主队列, 那么没有任何问题
*/
- (void)syncExceptMain
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_async(queue, ^{
    // 该block会在子线程中执行
        NSLog(@"%@", [NSThread currentThread]);
        dispatch_queue_t queue = dispatch_get_main_queue();
        dispatch_sync(queue, ^{
    // 该block会在主线程执行
            NSLog(@"%@", [NSThread currentThread]);
        });
    });
}

/*
如果是在主线程中调用同步函数 + 主队列, 那么会导致死锁
导致死锁的原因:
若在主线程中执行sync函数,此时sync内的block在等待主线程执行完毕,而sync本身有在被主线程执行,导致双发都无法执行完毕,造成死锁。
*/
- (void)syncMain
{
    NSLog(@"%@", [NSThread currentThread]);
    // 主队列:
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
    //此时内部代码在等待主线程执行完毕,而主线程又在执行该sync操作,从而造成死锁
        NSLog(@"----------");
        NSLog(@"%@", [NSThread currentThread]);
    });
}

/*
异步 + 主队列 : 任务在主线程中执行,且异步失效,只会同步执行
*/
- (void)asyncMain
{
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{ 
        NSLog(@"%@", [NSThread currentThread]);
    });
}

/*
同步 + 并发 : 不开启新的线程,只会在当前线程顺序执行
*/
- (void)syncConCurrent
{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_sync(queue, ^{
        NSLog(@"任务1  == %@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"任务2  == %@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"任务3  == %@", [NSThread currentThread]);
    });
}

/*
同步 + 串行: 不开启新的线程,只会在当前线程顺序执行
*/
- (void)syncSerial
{
    dispatch_queue_t queue = dispatch_queue_create("com.520it.lnj", NULL);
    dispatch_sync(queue, ^{
        NSLog(@"任务1  == %@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"任务2  == %@", [NSThread currentThread]);
    });

    dispatch_sync(queue, ^{  
        NSLog(@"任务3  == %@", [NSThread currentThread]);
    });
}

/*
异步 + 串行:会开启新的线程
但是只会开启一个新的线程
*/
- (void)asynSerial
{
/*
能够创建新线程的原因:我们是使用"异步"函数调用
只能创建1个子线程的原因:我们的队列是串行队列
*/
    dispatch_queue_t queue = dispatch_queue_create("自定义线程名", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"任务1  == %@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"任务2  == %@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"任务3  == %@", [NSThread currentThread]);
    });
}

/*
异步 + 并发 : 会开启多个线程并发执行
*/
- (void)asynConcurrent
{
    dispatch_queue_t queue = dispatch_queue_create("自定义线程名", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"任务1  == %@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"任务2  == %@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"任务3  == %@", [NSThread currentThread]);
    });
}

@end

/*

两个注意点:

当调用同步函数时,同步函数范围后的代码只能在同步函数全部执行完毕后执行;

当调用异步函数时,异步函数范围后的代码不用等函数执行完毕就会执行

*/

同步执行 异步执行
串行队列 不开线程,在当前线程,顺序执行 开1条线程,顺序执行
并发队列 不开线程,在当前线程,顺序执行 开多条线程,一起执行
主队列 不开线程,会造成死锁 不开线程,在主线程空闲的时候执行
全局队列 不开线程,在当前线程,顺序执行 开多条线程,一起执行

五、一次性执行及单例创建

  • 使用dispatch_once函数让某段代码在程序运行时只执行一次(通过函数内部的锁来保证线程安全)

  • 主要作用:实现单例模式


// 使用 dispatch_once 实现单例

+ (instancetype)sharedSingleton {
    static id instance;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

// 模拟dispatch_once来实现单例

// 使用互斥锁实现单例

+ (instancetype)sharedSync {
    static id syncInstance;
    @synchronized(self) {
        if (syncInstance == nil) {
            syncInstance = [[self alloc] init];
        }
    }
    return syncInstance;
}

//两种方法的性能对比
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    long largeNumber = 1000 * 1000;

// 测试互斥锁
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    for (long i = 0; i < largeNumber; ++i) {
        [Singleton sharedSync];
    }
    NSLog(@"互斥锁: %f", CFAbsoluteTimeGetCurrent() - start);

// 测试 dispatch_once
    start = CFAbsoluteTimeGetCurrent();
    for (long i = 0; i < largeNumber; ++i){
        [Singleton sharedSingleton];
    }
    NSLog(@"dispatch_once: %f", CFAbsoluteTimeGetCurrent() - start);
}

//结果表明用dispatch_once方法来实现单例模式效率远高与用互斥锁模拟实现

六、延迟操作

  • 通过dispatch_after函数实现延缓代码执行

#pragma mark - 延迟执行

- (void)delay {
/**

方法解释:

从现在开始,经过多少纳秒,由"队列"调度  异步执行 block 中的代码

参数

1. when    从现在开始,经过多少纳秒

2. queue   队列

3. block   异步执行的任务

*/

    dispatch_time_t when = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC));
    void (^task)() = ^ {
        NSLog(@"%@", [NSThread currentThread]);
  };

// 主队列

    dispatch_after(when, dispatch_get_main_queue(), task);

// 全局队列

//    dispatch_after(when, dispatch_get_global_queue(0, 0), task);

// 串行队列

//  dispatch_after(when, dispatch_queue_create("itheima", NULL), task);

NSLog(@"come here");

}

七、栅栏调度和线程通信示例

首先先配置下plist文件

info.plist

- (void)barrier
{
    dispatch_queue_t queue = dispatch_queue_create("队列名", DISPATCH_QUEUE_CONCURRENT);
//    dispatch_queue_t queue2 = dispatch_queue_create("队列名", DISPATCH_QUEUE_CONCURRENT);
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    __block UIImage *image1 = nil;
    __block UIImage *image2 = nil;

// 1.开启一个新的线程下载第一张图片
    dispatch_async(queue, ^{
        NSURL *url = [NSURL URLWithString:@"http://www.shaimn.com/uploads/allimg/150509/1-150509233453.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        image1 = image;
        NSLog(@"图片1下载完毕");
    });

// 2.开启一个新的线程下载第二张图片
    dispatch_async(queue, ^{
        NSURL *url = [NSURL URLWithString:@"http://m.818today.com/imgsy/image/2016/0215/6359115208730406558041085.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        image2 = image;
        NSLog(@"图片2下载完毕");
    });

// 3.开启一个新的线程, 合成图片
// 栅栏
    dispatch_barrier_async(queue, ^{
// 图片下载完毕
        NSLog(@"%@ %@", image1, image2);
// 1.开启图片上下文
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
// 2.将第一张图片画上去
        [image1 drawInRect:CGRectMake(0, 0, 100, 200)];
// 3.将第二张图片画上去
        [image2 drawInRect:CGRectMake(100, 0, 100, 200)];
// 4.从上下文中获取绘制好的图片
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
// 5.关闭上下文
        UIGraphicsEndImageContext();
// 6.回到主线程更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = newImage;
        });
    NSLog(@"栅栏执行完毕了");
    });
    dispatch_async(queue, ^{
        NSLog(@"---------");
    });
    dispatch_async(queue, ^{
        NSLog(@"---------");
    });
    dispatch_async(queue, ^{
        NSLog(@"---------");
    });
}

八、调度组

功能:dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。调度组能实现的栅栏也可实现。

代码示例:异步线程中,下载两种图片,在dispatch_group_notify通知中说明图片下载完-> 合并图片,合并图片结束后 ->再回到主线程中更新UI


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    NSLog(@"%s", __func__);
    dispatch_queue_t queue = dispatch_queue_create("队列名", DISPATCH_QUEUE_CONCURRENT);
    __block UIImage *image1 = nil;
    __block UIImage *image2 = nil;
    dispatch_group_t group = dispatch_group_create();
// 1.开启一个新的线程下载第一张图片
    dispatch_group_async(group, queue, ^{
        NSURL *url = [NSURL URLWithString:@"http://www.shaimn.com/uploads/allimg/150509/1-150509233453.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        image1 = image;
        NSLog(@"图片1下载完毕");
    });

// 2.开启一个新的线程下载第二张图片
    dispatch_group_async(group, queue, ^{
        NSURL *url = [NSURL URLWithString:@"http://m.818today.com/imgsy/image/2016/0215/6359115208730406558041085.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [UIImage imageWithData:data];
        image2 = image;
        NSLog(@"图片2下载完毕");
    });

// 3.开启一个新的线程, 合成图片
// 只要将队列放到group中, 队列中的任务执行完毕, group就会发出一个通知
    dispatch_group_notify(group, queue, ^{
        NSLog(@"%@ %@", image1, image2);

// 1.开启图片上下文
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));

// 2.将第一张图片画上去
        [image1 drawInRect:CGRectMake(0, 0, 100, 200)];

// 3.将第二张图片画上去
        [image2 drawInRect:CGRectMake(100, 0, 100, 200)];

// 4.从上下文中获取绘制好的图片
        UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

// 5.关闭上下文
        UIGraphicsEndImageContext();

// 4.回到主线程更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = newImage;
         });
    });
}

调度组的实现原理:


#pragma mark - dispatch_group_async 的实现原理

//在终端输入命令:man dispatch_group_async可得到如下信息

/*

The dispatch_group_async() convenience function behaves like so:

void

dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)

{

dispatch_retain(group);

dispatch_group_enter(group);

dispatch_async(queue, ^{

block();

dispatch_group_leave(group);

dispatch_release(group);

});

}

*/

// MARK: - 仿照上述信息来实现group原理

- (void)group2 {

// 1. 调度组
    dispatch_group_t group = dispatch_group_create();

// 2. 队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

// 3. 将任务添加到队列
// 进入群组
    dispatch_group_enter(group);
    dispatch_async(q, ^{
        NSLog(@"任务 1 %@", [NSThread currentThread]);
// 离开群组
        dispatch_group_leave(group);
});

// 进入群组
    dispatch_group_enter(group);
    dispatch_async(q, ^{
        NSLog(@"任务 2 %@", [NSThread currentThread]);
// 离开群组
        dispatch_group_leave(group);
    });

// 进入群组
    dispatch_group_enter(group);
    dispatch_async(q, ^{
        NSLog(@"任务 3 %@", [NSThread currentThread]);
// 离开群组
        dispatch_group_leave(group);
    });

// 4. 监听所有任务完成
//    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//        NSLog(@"OVER %@", [NSThread currentThread]);
//    });

// dispatch_group_wait 是同步的,DISPATCH_TIME_FOREVER参数表示一直等待到前面执行完毕

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

// 5. 判断异步
    NSLog(@"come here");
}

九、快速迭代(类似for循环)

  • 功能:使用dispatch_apply函数能进行快速迭代遍历,但是它和for循环又有些不同,for是按顺序循环,而_apply函数是随机调度

for (int index = 0;index < 100;index ++){

    //在当前线程执行100次代码, index是从0到99

}

dispatch_apply(100, 队列queue, ^(size_t index){

    //开启多个线程来执行100次代码,index顺序不确定
  //相比于for循环执行的效率高

});

/*

第一个参数: 设置要执行几次

第二个参数: 决定第三个参数的block在哪个线程中执行

第三个参数: 调用的代码,其中参数i

*/

十、信号量(dispatch_semaphore)

功能:使用dispatch_semaphore_signal使信号量资源+1,设置dispatch_semaphore_wait等待来判断信号量是否为0,不为0时使信号量资源-1并返回,为0时继续执行,从而达到线程同步的目的和同步锁一样能够解决资源抢占的问题

//通过对象方法直接返回数组(将从网络获取数据的过程要在子线程中)
- (__kindof NSArray  *)getData{

        //参数0表示一开始创建的信号量内部是没有资源的
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

        //创建用来存放数据的数组
    NSMutableArray * array = [NSMutableArray arrayWithCapacity:0];
        //用全局队列来加载数据
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i=0; i<10; i++) {
            //模拟加载延时
            [NSThread sleepForTimeInterval:1.0];
            [array addObject:[NSNumber numberWithInt:i]];
        }
            //发送信号,使信号量资源数+1
        dispatch_semaphore_signal(semaphore);

    });
        //信号等待,判断信号量资源数,不为0时就阻塞当前线程,并使资源数-1,循环执行
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);


    return array;
}

调度组也可以实现上述功能

- (__kindof NSArray  *)getData{

    NSMutableArray * array = [NSMutableArray arrayWithCapacity:0];

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
        [NSThread sleepForTimeInterval:1.0];
        for (int i=0; i<10; i++) {
            [array addObject:[NSNumber numberWithInt:i]];
        }
    });
    //阻塞当前线程,直到group的内容全部执行完成
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    return array;
}

GCD和NSOperation的一些区别

  • GCD是底层的C语言构成的API,而NSOperationQueue及相关对象是Objc的对象。在GCD中,在队列中执行的是由block构成的任务,这是一个轻量级的数据结构;而Operation作为一个对象,为我们提供了更多的选择;
  • 在NSOperationQueue中,我们可以随时取消已经设定要准备执行的任务(当然,已经开始的任务就无法阻止了),而GCD没法停止已经加入queue的block(其实是有的,但需要许多复杂的代码);
  • NSOperation能够方便地设置依赖关系,我们可以让一个Operation依赖于另一个Operation,这样的话尽管两个Operation处于同一个并行队列中,但前者会直到后者执行完毕后再执行;
  • 我们能将KVO应用在NSOperation中,可以监听一个Operation是否完成或取消,这样子能比GCD更加有效地掌控我们执行的后台任务;
  • 在NSOperation中,我们能够设置NSOperation的priority优先级,能够使同一个并行队列中的任务区分先后地执行,而在GCD中,我们只能区分不同任务队列的优先级,如果要区分block任务的优先级,也需要大量的复杂代码;
    我们能够对NSOperation进行继承,在这之上添加成员变量与成员方法,提高整个代码的复用度,这比简单地将block任务排入执行队列更有自由度,能够在其之上添加更多自定制的功能。

你可能感兴趣的:(iOS 一篇文章搞定GCD)