OC中使用多线程的4中方式:

一. 多线程基础
1. 进程
进程是指在系统中正在运行的一个应用程序。每个进程之间是独立的,每个进程均运行在其专用且受保护的内存空间内。
2.线程
进程要想执行任务,必须得有线程(进程至少有1条线程,称为主线程),进程的所有任务都在线程中执行。
3. 进程和线程的比较
1.线程是CPU 执行任务的最小单位。
2.进程是CPU 分配资源的最小单位。
3.一个进程中至少要有一个线程。
4.同一个进程内的线程共享进程的资源。
4. 线程的串行
1个线程中任务的执行是串行的
如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务
也就是说,在同一时间内,1个线程只能执行1个任务
5. 多线程
1个进程中可以开启多条线程,每条线程可以并行(同时)执行不同的任务
多线程技术可以提高程序的执行效率
6. 多线程原理
同一时间,CPU只能处理1条线程,只有1条线程在工作(执行),多线程并发(同时)执行,其实是CPU快速地在多条线程之间调度(切换),如果CPU调度线程的时间足够快,就造成了多线程并发执行的假象。
- 那么如果线程非常非常多,会发生什么情况?
CPU会在N多线程之间调度,CPU会累死,消耗大量的CPU资源,同时每条线程被调度执行的频次也会会降低(线程的执行效率降低)。
因此我们一般只开3-5条线程。
7. 多线程优缺点
多线程的优点:
- 能适当提高程序的执行效率
- 能适当提高资源利用率(CPU、内存利用率)
多线程的缺点:
- 程序设计更加复杂:比如线程之间的通信、多线程的数据共享等问题。
- 创建线程是有开销的。
(iOS下主要成本包括:内核数据结构(大约1KB)、栈空间(子线程512KB、主线程1MB,也可以使用-setStackSize:设置,但必须是4K的倍数,而且最小是16K),创建线程大约需要90毫秒的创建时间
如果开启大量的线程,会降低程序的性能,线程越多,CPU在调度线程上的开销就越大。)
8. 多线程的应用
主线程的主要作用
- 显示\刷新UI界面。
- 处理UI事件(比如点击事件、滚动事件、拖拽事件等)。
主线程的使用注意
- 别将比较耗时的操作放到主线程中,耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验。将耗时操作放在子线程中执行,提高程序的执行效率。
GCD(Grand Central Dispatch)
内容目录:
- 1.同步、异步与串行、并发
- 2.GCD线程间的通信
- 3.栅栏函数
- 4.延迟执行
- 5.一次性代码(单例)
- 6.快速迭代(类似于for循环)
- 7.队列组(同栅栏函数)
1.同步、异步与串行、并发
同步:只能在当前线程中执行任务,不具备开启新线程的能力,任务立刻马上执行,会阻塞当前线程并等待 Block中的任务执行完毕,然后当前线程才会继续往下运行
异步:可以在新的线程中执行任务,具备开启新线程的能力,但不一定会开新线程,当前线程会直接往下执行,不会阻塞当前线程

2.GCD线程间的通信
我们同样通过一个实例来看
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation ViewController -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{ // 获得图片URL
NSURL *url = [NSURL URLWithString:@"img_url"]; // 将图片URL下载为二进制文件
NSData *data = [NSData dataWithContentsOfURL:url]; // 将二进制文件转化为image
UIImage *image = [UIImage imageWithData:data];
NSLog(@"%@",[NSThread currentThread]); // 返回主线程 这里用同步函数不会发生死锁,因为这个方法在子线程中被调用。 // 也可以使用异步函数
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"%@",[NSThread currentThread]);
});
});
}
@end
GCD线程间的通信非常简单,使用同步或异步函数,传入主队列即可。
3.栅栏函数(控制任务的执行顺序,拦截之前的多线程任务)
dispatch_barrier_async(queue, ^{
NSLog(@"--dispatch_barrier_async--");
});
栅栏函数的作用:
// 创建队列(并发队列)
dispatch_queue_t queue = dispatch_queue_create("com.xxccqueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (NSInteger i = 0; i<3; i++) {
NSLog(@"1 : %@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (NSInteger i = 0; i<3; i++) {
NSLog(@"2 : %@",[NSThread currentThread]);
}
});
NSLog(@"1");
// 栅栏函数
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async");
});
NSLog(@"2");
dispatch_async(queue, ^{
for (NSInteger i = 0; i<3; i++) {
NSLog(@"3 : %@",[NSThread currentThread]);
}
});
// 输出
GCD[53203:1418808] 1
GCD[53203:1418838] 1 : <NSThread: 0x600002dd3b40>{number = 3, name = (null)}
GCD[53203:1418840] 2 : <NSThread: 0x600002dd3d00>{number = 4, name = (null)}
GCD[53203:1418808] 2
GCD[53203:1418840] 2 : <NSThread: 0x600002dd3d00>{number = 4, name = (null)}
GCD[53203:1418838] 1 : <NSThread: 0x600002dd3b40>{number = 3, name = (null)}
GCD[53203:1418840] 2 : <NSThread: 0x600002dd3d00>{number = 4, name = (null)}
GCD[53203:1418838] 1 : <NSThread: 0x600002dd3b40>{number = 3, name = (null)}
GCD[53203:1418838] dispatch_barrier_async
GCD[53203:1418838] 3 : <NSThread: 0x600002dd3b40>{number = 3, name = (null)}
GCD[53203:1418838] 3 : <NSThread: 0x600002dd3b40>{number = 3, name = (null)}
GCD[53203:1418838] 3 : <NSThread: 0x600002dd3b40>{number = 3, name = (null)}
4.延迟执行(延迟·控制在哪个线程执行)
/* 第一个参数:延迟时间
第二个参数:要执行的代码
如果想让延迟的代码在子线程中执行,也可以更改在哪个队列中执行
dispatch_get_main_queue() -> dispatch_get_global_queue(0, 0) */
dispatch_after(<#dispatch_time_t when#>, <#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
//
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"---%@",[NSThread currentThread]);
});
延迟执行的其他方法:
// 2s中之后调用run方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];
// repeats:YES 是否重复
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
5.一次性代码(单例)
//整个程序运行过程中只会执行一次 //onceToken用来记录该部分的代码是否被执行过
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"-----");
});
6.快速迭代(开多个线程并发完成迭代操作)
/* 第一个参数:迭代的次数
第二个参数:在哪个队列中执行
第三个参数:block要执行的任务 */
dispatch_apply(5, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSLog(@"index:%zd",index);
});
// 输出
GCD[52316:1396310] index:1
GCD[52316:1396267] index:0
GCD[52316:1396311] index:2
GCD[52316:1396312] index:3
GCD[52316:1396310] index:4
快速迭代:开启多条线程,并发执行。但是开启线程是需要耗时的,所以不会比for循环快,而且打印结果显示是乱序的。
7.队列组(同栅栏函数)
// 创建队列组
dispatch_group_t group = dispatch_group_create(); // 创建并行队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0); // 执行队列组任务
dispatch_group_async(group, queue, ^{
NSLog(@"1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"2");
});
//队列组中的任务执行完毕之后,执行该函数
dispatch_group_notify(group, queue, ^{
NSLog(@"3");
});
示例:两张图片都下载完成后,才进行拼接操作。
#import "ViewController.h"
@interface ViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (nonatomic, strong) UIImage *image1; /**< 图片1 */
@property (nonatomic, strong) UIImage *image2; /**< 图片2 */
@end
@implementation ViewController
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
//创建队列组
dispatch_group_t group = dispatch_group_create();
//下载图片1
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^{
//1.获取url地址
NSURL *url = [NSURL URLWithString:@"http://www.huabian.com/uploadfile/2015/0914/20150914014032274.jpg"];
//2.下载图片
NSData *data = [NSData dataWithContentsOfURL:url];
//3.把二进制数据转换成图片
self.image1 = [UIImage imageWithData:data];
NSLog(@"1---%@",self.image1);
});
//下载图片2
dispatch_group_async(group, queue, ^{
//1.获取url地址
NSURL *url = [NSURL URLWithString:@"http://img1.3lian.com/img2011/w12/1202/19/d/88.jpg"];
//2.下载图片
NSData *data = [NSData dataWithContentsOfURL:url];
//3.把二进制数据转换成图片
self.image2 = [UIImage imageWithData:data];
NSLog(@"2---%@",self.image2);
});
//合成,队列组执行完毕之后执行
dispatch_group_notify(group, queue, ^{
//开启图形上下文
UIGraphicsBeginImageContext(CGSizeMake(200, 200));
//画1
[self.image1 drawInRect:CGRectMake(0, 0, 200, 100)];
//画2
[self.image2 drawInRect:CGRectMake(0, 100, 200, 100)];
//根据图形上下文拿到图片
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
//关闭上下文
UIGraphicsEndImageContext();
//回到主线程刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
NSLog(@"%@--刷新UI",[NSThread currentThread]);
});
});
}
@end
NSOperation
👏👏👏欢迎大家加入群组(IT_大前端技术交流群),技术交流群

网友评论