iOS-多线程编程之GCD

时间:2022-04-19 05:18:53

引言:

小白一枚,在自学做微博项目的时候,发现从服务器获取用户的token之后跳转控制器,发现要等7~10秒左右才能开始调到首页控制器(或者版本新特性控制器),为什么呢?


原来,我一直都在主线程(UI线程)里面做UI操作和数据的操作。如此一来,主线程要处理这么多的东西,当然要等待啦。而且,主线程的任务是在特殊的串行队列中运行的。

GCD中,任何线程都是放在队列中执行的。队列是FIFO(First In First Out),顾名思义,先进先出。

而队列分为:串行队列、并发队列

  • 串行队列:任务一个接着一个执行,先后执行。
  • 并发队列:可以多个任务并发执行,并且是自动开启多个线程同时执行任务。PS:并发的功能要在异步情况下才有效。

好了,既然我已经知道是主线程是在串行队列中,那么我们获取数据,把数据模型化,归档等处理放在异步并发队列中,而跳转控制器放在主线程中不就行啦~~~~

首先我们来了解一下GCD的一些常用的函数:

    //获得主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
    //获得全局的并发队列
//第一个参数选择默认的优先级,也可以选择优先级高的和优先级低的,第二个参数默认是0。
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

async开头的是异步函数,当然就是在新的线程执行任务,具备创建新的线程能力。而sync开头的是同步函数,只能在当前线程中执行任务,不具备开启新线程的能力。


异步 + 主队列:

/**
主队列情况:不会创建新的线程

@param mainQueue 主队列
*/

-(void)asynMainQueue:(dispatch_queue_t)mainQueue
{
dispatch_async(mainQueue, ^{
NSLog(@"---下载1---%@",[NSThread currentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"---下载2---%@",[NSThread currentThread]);
}); dispatch_async(mainQueue, ^{
NSLog(@"---下载3---%@",[NSThread currentThread]);
});
}

2017-06-09 22:38:50.045 GCD[2791:335754] —下载1—NSThread: 0x608000260440{number = 1, name = main}
2017-06-09 22:38:50.045 GCD[2791:335754] —下载2—NSThread: 0x608000260440{number = 1, name = main}
2017-06-09 22:38:50.046 GCD[2791:335754] —下载3—NSThread: 0x608000260440{number = 1, name = main}

输出结果,number = 1,并且线程名字是main,而且线程号全为:0x608000260440,所以只有一个主线程,并且是串行执行,验证了开篇说的话。

异步 + 全局并发队列:

/**
全局并发队列的情况:会创建新的线程

@param globalQueue 全局并发队列
*/

-(void)asynGlobalQueue:(dispatch_queue_t)globalQueue
{
dispatch_async(globalQueue, ^{
NSLog(@"---下载4---%@",[NSThread currentThread]);
});
dispatch_async(globalQueue, ^{
NSLog(@"---下载5---%@",[NSThread currentThread]);
});
dispatch_async(globalQueue, ^{
NSLog(@"---下载6---%@",[NSThread currentThread]);
});
}

2017-06-09 22:59:03.434 GCD[2975:360019] —下载5—NSThread: 0x6080002684c0{number = 4, name = (null)}
2017-06-09 22:59:03.434 GCD[2975:360002] —下载4—NSThread: 0x6000002662c0{number = 3, name = (null)}
2017-06-09 22:59:03.434 GCD[2975:360004] —下载6—NSThread: 0x600000266300{number = 5, name = (null)}

输出结果显示:三个线程编号都不一样,所以,这里自动开启了三条新的线程来执行这个NSLog任务。虽然number = 5,可能有点疑惑,3条新的线程加上1条主线程,不应该是4条线程才对嘛。我也很疑惑,但是一切还是以线程编号为准,至少是创建了3条新的线程,并且执行顺序具有随机性。

异步 + 串行队列:

/**
异步串行队列的情况:会创建新的线程
*/

-(void)asynSerialQueue
{
//创建串行队列
/*
第二个参数:DISPATCH_QUEUE_SERIAL/NULL 表示创建一个串行队列
DISPATCH_QUEUE_CONCURRENT 表示创建一个并发队列
*/

dispatch_queue_t queue = dispatch_queue_create("HZhenF", DISPATCH_QUEUE_SERIAL);
//添加任务到队列中,执行
dispatch_async(queue, ^{
NSLog(@"---下载7---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"---下载8---%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"---下载9---%@",[NSThread currentThread]);
});
}

2017-06-09 23:03:18.802 GCD[2996:363942] —下载7—NSThread: 0x608000072fc0{number = 3, name = (null)}
2017-06-09 23:03:18.802 GCD[2996:363942] —下载8—NSThread: 0x608000072fc0{number = 3, name = (null)}
2017-06-09 23:03:18.802 GCD[2996:363942] —下载9—NSThread: 0x608000072fc0{number = 3, name = (null)}

三个线程的编号都一样,所以只系统只是创建了一个新的线程来执行这个任务,并且串行执行这些任务。


下面,拓展一些个人觉得比较有用的GCD综合应用:

一、有时候,我们想点击按钮后,想延迟一些时间再执行对应事件。下面演示,事件会在3秒后被触发。

/**
全局并发队列延迟执行情况

@param globalQueue 全局队列
*/

-(void)asynGlobalQueueDelay:(dispatch_queue_t)globalQueue
{
// //设置任务推迟执行时间
// dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0*NSEC_PER_SEC));
//在指定时间点,执行队列的任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), globalQueue, ^{
NSLog(@"HZhenF是最棒的!");
});
}

二、我们只想某些代码只是在程序中运行一遍:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"我只会运行一遍!");
});
}

三、在全局并发队列中处理一些数据和其他耗时操作,在主队列中刷新UI、跳转控制器。(这个就是我开篇遇到的问题,用这种就能解决。在全局并发队列中对服务器返回的数据进行模型化和归档,然后在主线程跳转控制器即可)

/**
全局并发队列处理数据,主队列刷新UI

@param globalQueue 全局并发队列
*/

-(void)asynHandleInGlobalQueueAndRefreshInMainQueue:(dispatch_queue_t)globalQueue
{
dispatch_async(globalQueue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://ss3.baidu.com/9fo3dSag_xI4khGko9WTAnF6hhy/image/h%3D200/sign=d6bda411840a19d8d403830503fb82c9/e7cd7b899e510fb315268a37dd33c895d1430c56.jpg"]];
UIImage *image = [UIImage imageWithData:data];

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

}

四、想执行一组不同的操作,最后才执行结束操作:

/**
组队列执行完,再执行后续操作

@param globalQueue 全局并发队列
*/

-(void)asyncHandleInGroupQueueThenHanleOther:(dispatch_queue_t)globalQueue
{
//创建一个组
dispatch_group_t group = dispatch_group_create();
__block int a,b;
dispatch_group_async(group, globalQueue, ^{
a = 1;
});
dispatch_group_async(group, globalQueue, ^{
b = 1;
});
//组队列执行完了,再执行其他操作
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
a = a + b;
NSLog(@"a = %d",a);
});
}

代码中为什么要使用__block的原因:在block中,要修改外界的值,必须在外界变量前面加上__block。

顺便说下变量的存储方式:
- 外界的变量是存在栈中,而block是存在堆中。
- 外界变量不加__block的情况下(栈中),block中可以访问外界的变量,但是不能修改外界的变量。不加__block是值传递,所以block内部不能修改。而加上__block访问的是地址,所以可以修改(这些要C++代码编译才能看到)
- block可以定义和外界同名变量,此时block的同名变量是存在堆中,和外界在栈中的变量不影响。