美文网首页iOS学习专题将来跳槽用
iOS 多线程详解(GCD与NSOperation)

iOS 多线程详解(GCD与NSOperation)

作者: num_one | 来源:发表于2019-03-01 12:33 被阅读43次

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_大前端技术交流群),技术交流群
IT_大前端技术交流群

相关文章

网友评论

    本文标题:iOS 多线程详解(GCD与NSOperation)

    本文链接:https://www.haomeiwen.com/subject/lreruqtx.html