GCD相关

什么是GCD?

全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”

纯C语言,提供了非常多强大的函数

GCD的优势

GCD是苹果公司为多核的并行运算提出的解决方案

GCD会自动利用更多的CPU内核(比如双核、四核)

GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

GCD中有2个核心概念

(1)任务:执行什么操作

(2)队列:用来存放任务

GCD的使用就2个步骤

(1)定制任务

(2)确定想做的事情

将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行

提示:任务的取出遵循队列的FIFO原则:先进先出,后进后出

执行任务

1.GCD中有2个用来执行任务的函数

说明:把右边的参数(任务)提交给左边的参数(队列)进行执行。

(1)用同步的方式执行任务

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

参数说明:

queue:队列

block:任务

(2)用异步的方式执行任务

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

同步和异步的区别

同步:在当前线程中执行

异步:在另一条线程中执行

队列

队列的类型

GCD的队列可以分为2大类型

(1)并发队列(Concurrent Dispatch Queue)

可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效

创建一个并发队列

dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_CONCURRENT);

(2)串行队列(Serial Dispatch Queue)

让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

创建一个串行队列

dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_SERIAL);

全局并发队列

GCD默认提供了全局并发队列,供整个应用使用,可以无需手动创建

使用dispatch_get_global_queue函数获得全局的并发队列

//参数1:队列的优先级
//参数2:此参数暂时无用,为0即可
dispatch_queue_t dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags);

获得全局并发队列

/*
 The global concurrent queues may still be identified by their priority,
 * which map to the following QOS classes:
 *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED //优先级高
 *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT //优先级中等(默认)
 *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY //优先级低
 *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND //优先级在后台,最低
 */
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

主队列(跟主线程相关的队列)

  • 主队列是GCD自带的一种特殊串行队列
  • 放在主队列中的任务,都会放到主线程中执行
  • 使用dispatch_get_main_queue()获得主队列

补充说明

有4个术语比较容易混淆:同步、异步、并发、串行

同步和异步决定了要不要开启新的线程

同步:在当前线程中执行任务,不具备开启新线程的能力

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

并发和串行决定了任务的执行方式

并发:多个任务并发(同时)执行

串行:一个任务执行完毕后,再执行下一个任务

//异步函数+并发队列:会开启多条线程,队列中的任务是异步执行
- (void)asyncConcurrent
{
    dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"download1---%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"download2---%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"download3---%@",[NSThread currentThread]);
    });
}

/* 执行的顺序由GCD决定,开机条线程也有GCD决定
  2016-11-30 17:51:31.516978 GCDQueueTest[1698:510701] download3---<NSThread: 0x17406a100>{number = 3, name = (null)}
  2016-11-30 17:51:31.517236 GCDQueueTest[1698:510701] download2---<NSThread: 0x17406a100>{number = 4, name = (null)}
  2016-11-30 17:51:31.517404 GCDQueueTest[1698:510701] download2---<NSThread: 0x17406a100>{number = 2, name = (null)}
 */

//异步函数+串行队列:会开启一条线程,队列中的任务是串行执行的
- (void)asyncSerial
{
    dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_SERIAL);

    dispatch_async(queue, ^{
        NSLog(@"download1---%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"download2---%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"download3---%@",[NSThread currentThread]);
    });
}

/*
  2016-11-30 17:48:38.858472 GCDQueueTest[1689:509194] download1---<NSThread: 0x17406d480>{number = 3, name = (null)}
  2016-11-30 17:48:38.858699 GCDQueueTest[1689:509194] download2---<NSThread: 0x17406d480>{number = 3, name = (null)}
  2016-11-30 17:48:38.858783 GCDQueueTest[1689:509194] download3---<NSThread: 0x17406d480>{number = 3, name = (null)}
 */

//同步函数+并行队列:不会开线程,队列中的任务是串行执行的
- (void)syncConcurrent
{
    dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queue, ^{
        NSLog(@"download1---%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"download2---%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"download3---%@",[NSThread currentThread]);
    });
}

/*
  2016-11-30 17:47:35.205883 GCDQueueTest[1686:508612] download1---<NSThread: 0x17406ab80>{number = 1, name = main}
  2016-11-30 17:47:35.206162 GCDQueueTest[1686:508612] download2---<NSThread: 0x17406ab80>{number = 1, name = main}
  2016-11-30 17:47:35.206326 GCDQueueTest[1686:508612] download3---<NSThread: 0x17406ab80>{number = 1, name = main}
*/

//同步函数+串行队列:不会开线程,队列中的任务是串行执行的
- (void)syncSerial
{
    dispatch_queue_t queue = dispatch_queue_create("com.flashloft.download", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(queue, ^{
        NSLog(@"download1---%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"download2---%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"download3---%@",[NSThread currentThread]);
    });
}

/*输出
  2016-11-30 17:46:15.371174 GCDQueueTest[1684:508139] download1---<NSThread: 0x17006f680>{number = 1, name = main}
  2016-11-30 17:46:15.371319 GCDQueueTest[1684:508139] download2---<NSThread: 0x17006f680>{number = 1, name = main}
  2016-11-30 17:46:15.371392 GCDQueueTest[1684:508139] download3---<NSThread: 0x17006f680>{number = 1, name = main}
*/

//异步函数 + 主队列,虽然具备开子线程的条件,但主队列必须要在主线程中执行,所以没有必要开线程
- (void)asyncMain
{
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_async(queue, ^{
        NSLog(@"download1---%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"download2---%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"download3---%@",[NSThread currentThread]);
    });
}

/*
 2016-11-30 23:29:54.734 GCDQueueTest[5098:181614] download1---<NSThread: 0x6000000749c0>{number = 1, name = main}
 2016-11-30 23:29:54.736 GCDQueueTest[5098:181614] download2---<NSThread: 0x6000000749c0>{number = 1, name = main}
 2016-11-30 23:29:54.743 GCDQueueTest[5098:181614] download3---<NSThread: 0x6000000749c0>{number = 1, name = main}
 */

//同步函数 + 主队列 造成死锁。
//主队列的特点:如果主队列发现当前主线程中有任务正在执行,主队列则会停止调用队列中的任务,直到主队列空闲为止
- (void)syncMain
{
    dispatch_queue_t queue = dispatch_get_main_queue();
    NSLog(@"start----------");
    dispatch_sync(queue, ^{
        NSLog(@"download1---%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"download2---%@",[NSThread currentThread]);
    });

    dispatch_sync(queue, ^{
        NSLog(@"download3---%@",[NSThread currentThread]);
    });
}

//但如果上面函数放在一个子线程中执行,不会造成死锁
//比如:NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(syncMain) object:nil];
//[thread start];
/*
 2016-11-30 23:33:06.238 GCDQueueTest[5297:188125] download1---<NSThread: 0x608000068080>{number = 1, name = main}
 2016-11-30 23:33:06.239 GCDQueueTest[5297:188125] download2---<NSThread: 0x608000068080>{number = 1, name = main}
 2016-11-30 23:33:06.239 GCDQueueTest[5297:188125] download3---<NSThread: 0x608000068080>{number = 1, name = main}
*/

总结上述

小结

说明:同步函数不具备开启线程的能力,无论是什么队列都不会开启线程;异步函数具备开启线程的能力,开启几条线程由队列决定(串行队列只会开启一条新的线程,并发队列会开启多条线程)。

同步函数

(1)并发队列:不会开线程

(2)串行队列:不会开线程

异步函数

(1)并发队列:能开启N条线程

(2)串行队列:开启1条线程

补充:

凡是函数中,各种函数名中带有create\copy\new\retain等字眼,都需要在不需要使用这个数据的时候进行release。 GCD的数据类型在ARC的环境下不需要再做release。 CF(core Foundation)的数据类型在ARC环境下还是需要做release。 异步函数具备开线程的能力,但不一定会开线程

GCD延迟执行的方法以及其他延迟执行的方法

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self delay];
}

- (void)delay
{
    NSLog(@"-----start-----");

    //1.第一种延迟执行的方法
    //[self performSelector:@selector(task) withObject:nil afterDelay:2.0];

    //2.第二种延迟执行的方法
    //[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(task) userInfo:nil repeats:YES];

    //3.GCD的延时执行的方法
    //参数1:DISPATCH_TIME_NOW 从现在开始计算时间
    //参数2:延迟的时间 乘以 GCD时间单位(纳秒)
    //参数3:执行的队列,比如 dispatch_get_main_queue()
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"GCD--------%@",[NSThread currentThread]);
    });
}

- (void)task
{
    NSLog(@"task--------%@",[NSThread currentThread]);
}

@end

GCD一次性代码

需求:点击控制器只有第一次点击的时候才打印。

- (void)once
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"这里执行一次性的代码");
    });
}

缺点:这是一个对象方法,如果又创建一个新的控制器,那么打印代码又会执行,因为每个新创建的控制器都有自己的布尔类型,且新创建的默认为NO,因此不能保证改行代码在整个程序中只打印一次。

注意,懒加载中不能使用一次性代码

GCD栅栏函数

什么是dispatch_barrier_async函数

毫无疑问,dispatch_barrier_async函数的作用与barrier的意思相同,在进程管理中起到一个栅栏的作用,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行,该函数需要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用

dispatch_barrier_async函数的作用

1.实现高效率的数据库访问和文件访问

2.避免数据竞争

注意:栅栏函数对全局并发队列无效,只能用于自己创建的并发队列

dispatch_barrier_async实例

- (void)barrier
{
    dispatch_queue_t queue = dispatch_queue_create("download", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"---download1---%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"---download2---%@",[NSThread currentThread]);
    });

    dispatch_barrier_sync(queue, ^{
        NSLog(@"++++++++++++++++barrier+++++++++++++++++++");
    });

    dispatch_async(queue, ^{
        NSLog(@"---download3---%@",[NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"---download4---%@",[NSThread currentThread]);
    });

}

输出结果:1 2 --> barrier -->3 4 其中12 与 34 由于并行处理先后顺序不定

GCD快速迭代函数

dispathc_apply 是dispatch_sync 和dispatch_group的关联API.它以指定的次数将指定的Block加入到指定的队列中。并等待队列中操作全部完成.

- (void)apply
{
    //dispatch_apply(size_t iterations, dispatch_queue_t  _Nonnull queue, ^(size_t))
    //参数1:迭代的次数
    //参数2:并发队列
    //参数3:index索引
    dispatch_apply(100000, dispatch_get_global_queue(0,0), ^(size_t index) {
        NSLog(@"%zd-----%@",index,[NSThread currentThread]);
    });
}

输出 index 顺序不确定,因为它是并行执行的(dispatch_get_global_queue是并行队列),但是done是在以上拷贝操作完成后才会执行,因此,它一般都是放在dispatch_async里面(异步)。实际上,这里 dispatch_apply如果换成串行队列上,则会依次输出index,但这样违背了我们想并行提高执行效率的初衷。

一个文件剪贴的操作用GCD方式实现的实例

- (void)moveFiles
{
    NSString *fromFolder = @"/Users/zhangfan/Desktop/from";
    NSString *toFolder = @"/Users/zhangfan/Desktop/to";
    NSArray *subPath = [[NSFileManager defaultManager] subpathsAtPath:fromFolder];

    NSInteger count = subPath.count;
//    如果使用for循环,全部使用主线程完成,这样如果操作大文件会导致整个程序一定时间内卡死
//    for(int i = 0; i < count; i++)
//    {
//        NSString *fromFullPath = [fromFolder stringByAppendingPathComponent:subPath[i]];
//        NSString *toFullPath = [toFolder stringByAppendingPathComponent:subPath[i]];
//        [[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
//        NSLog(@"已经将文件从%@移动到%@",fromFullPath,toFullPath);
//    }

    //如果交给GCD处理,会自动的分配子线程去处理
    dispatch_apply(count, dispatch_get_global_queue(0,0), ^(size_t index) {
        NSString *fromFullPath = [fromFolder stringByAppendingPathComponent:subPath[index]];
        NSString *toFullPath = [toFolder stringByAppendingPathComponent:subPath[index]];
        [[NSFileManager defaultManager] moveItemAtPath:fromFullPath toPath:toFullPath error:nil];
        NSLog(@"已经将文件从%@移动到%@",fromFullPath,toFullPath);
    });
}

GCD的队列组Group以及监听Group

有这么1种需求:

首先:分别异步执行2个耗时的操作

其次:等2个异步操作都执行完毕后,再回到主线程执行操作

如果想要快速高效地实现上述需求,可以考虑用队列组

- (void)group
{
    //获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //创建一个队列组
    dispatch_group_t gruop = dispatch_group_create();
    dispatch_group_async(gruop, queue, ^{
        NSLog(@"1---%@---",[NSThread currentThread]);
    });

    //执行一个耗时的异步操作
    dispatch_group_async(gruop, queue, ^{
        NSLog(@"2---%@---",[NSThread currentThread]);
    });

    //执行一个耗时的异步操作
    dispatch_group_async(gruop, queue, ^{
        NSLog(@"3---%@---",[NSThread currentThread]);
    });

    //这里监听group完成的情况,完成后做什么事情
    //这个方法是异步的,一定会等到group中最后一个任务执行完毕才会执行
    dispatch_group_notify(gruop, queue, ^{
        NSLog(@"已完成group中所有任务");
    });
}

另外一种队列组的写法

- (void)group2
{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t gruop = dispatch_group_create();

    //dispatch_group_enter方法将进入群组,该方法后的异步任务都会放入队列组的监听范围
    //dispatch_group_enter方法必须和dispatch_group_leave配对使用
    dispatch_async(queue, ^{
        NSLog(@"1---%@---",[NSThread currentThread]);
        dispatch_group_leave(gruop);
    });

    dispatch_group_enter(gruop);

    dispatch_async(queue, ^{
        NSLog(@"2---%@---",[NSThread currentThread]);
        dispatch_group_leave(gruop);
    });

    dispatch_group_enter(gruop);

    dispatch_async(queue, ^{
        NSLog(@"3---%@---",[NSThread currentThread]);
        dispatch_group_leave(gruop);
    });

    dispatch_group_notify(gruop, queue, ^{
        NSLog(@"已完成group中所有任务");
    });
}

关于组拦截的方法

//不会造成阻塞,方法本身是异步的
dispatch_group_notify(gruop, queue, ^{
        NSLog(@"已完成group中所有任务");
});
//会阻塞,直到group中所有的任务执行完,才会往后执行方法
dispatch_group_wait(gruop, DISPATCH_TIME_FOREVER);
NSLog(@"----end----");

一个使用GCD多线程队列组实现的下载2张图片,并且拼接成一张图片的实例

- (void)combineImages
{
    dispatch_group_t group = dispatch_group_create();

    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);

    dispatch_group_async(group, queue, ^{
        NSLog(@"download1-----%@",[NSThread currentThread]);
        NSURL *url = [NSURL URLWithString:@"http://easyread.ph.126.net/JT_gINsEoeyrXZF-nMyjPw==/7917057665058273673.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image1 = [UIImage imageWithData:data];
    });

    dispatch_group_async(group, queue, ^{
        NSLog(@"download2-----%@",[NSThread currentThread]);
        NSURL *url = [NSURL URLWithString:@"http://i1.hdslb.com/video/3f/3fbfa4d263b53926a5c4f5c8c9056260.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        self.image2 = [UIImage imageWithData:data];
    });

    dispatch_group_notify(group, queue, ^{
        NSLog(@"combine images-----%@",[NSThread currentThread]);
        UIGraphicsBeginImageContext(CGSizeMake(200, 200));
        [self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
        [self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndPDFContext();

        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"Show image -----%@",[NSThread currentThread]);
            self.imageView.image = image;
        });
    });
}

GCD中末尾带_f的函数

- (void)functionWithF
{
    /*
     * 参数1:队列
     * 参数2:参数3要传的函数需要带的参数,无参数为NULL即可
     * 参数3:要传的函数
     */
    dispatch_async_f(<#dispatch_queue_t  _Nonnull queue#>, <#void * _Nullable context#>, <#dispatch_function_t  _Nonnull work#>);
}

//dispatch_async_f参数3调用函数的格式:typedef void (*dispatch_function_t)(void *_Nullable);
void task(void *param)
{

}

GCD中的定时器

@interface ViewController ()

@property(strong,nonatomic)dispatch_source_t timer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    [self GCDTimer];
}

- (void)GCDTimer
{
    //1.创建GCD中的定时器
    /*
     * 参数1:source的类型DISPATCH_SOURCE_TYPE_TIMER 表示是定时器
     * 参数2:描述信息,线程ID
     * 参数3:更详细的描述信息
     * 参数4:队列,决定GCD定时器在哪个任务中执行
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));

    //2.设定定时器(起始时间|间隔时间|精准度)
    /*
     * 参数1:定时器对象
     * 参数2:起始时间,DISPATCH_TIME_NOW 从现在开始计时
     * 参数3:间隔时间,GCD中的时间单位为纳秒(NSEC_PER_SEC)
     * 参数4:精准度,0表示绝对精准
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

    //3.设置定时器执行的任务
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"GCD---%@",[NSThread currentThread]);
    });

    //4.启动执行
    dispatch_resume(timer);

    self.timer = timer;//设置为强引用才能工作,局部变量则会被释放
}
@end

GCD的定时器的优点:

  • 不会受RunLoop影响
  • 精准度高

results matching ""

    No results matching ""