ios基础篇(十二)—— 消息循环、GCD、任务和队列、串行、并行、Barrier阻塞、延时操作、一次性执行

 一、消息循环

  • 什么是消息循环
    • Runloop就是消息循环,每一个线程内部都有一个消息循环
    • 只有主线程的消息循环默认开启,子线程的消息循环默认不开启
  • 消息循环的目的
    • 保证程序不退出
    • 负责处理输入事件
    • 如果没有事件发生,会让程序进入休眠状态

1.1 消息循环-输入事件

  • 输入事件
    • Runloop接收输入事件来自两种不同的来源:输入源(input source)和定时源(timer source)
    • input for sources such as mouse and keyboard events from the window system, NSPort objects, and NSConnection objects. An NSRunLoop object also processes NSTimer events.

1.2 消息循环-消息循环模式

  • 通过定时器,复习消息循环的模式
    • 定时器执行的方法中不易执行太耗时的操作,否则就会降低用户体验,用户拖拽的时候会感觉到卡顿的现象
  • 消息循环的两种模式
    • NSDefaultRunLoopMode、NSRunLoopCommonModes
    • NSDefaultRunLoopMode
      • The mode to deal with input sources other than NSConnection objects.
      • This is the most commonly used run-loop mode.
      • Available in iOS 2.0 and later.
    • NSRunLoopCommonModes
      • Objects added to a run loop using this value as the mode are monitored by all run loop modes that have been declared as a member of the set of “common" modes; see the description of CFRunLoopAddCommonMode for details.

1.3 注意

  • 使用消息循环的时候必须指定两件事情
    • 输入事件:输入源和定时源
    • 消息循环模式
  • 消息循环运行在某一种消息循环模式上
  • 输入事件必须设置消息循环的模式,并且如果想让输入事件可以在消息循环上执行,输入事件的消息循环模式必须和当前消息循环的消息循环模式一致
- (void)viewDidLoad {
    [super viewDidLoad];
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(demo) userInfo:nil repeats:YES];       
    //把定时器添加到当前线程消息循环中
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    //消息循环是在一个指定的模式下运行的 默认的模式NSDefaultRunLoopMode,设置的输入事件也需要指定一个模式,消息循环的模式必须和输入事件的模式匹配才会执行   
    //UITrackingRunLoopMode  当滚动scrollView的时候,消息循环的模式自动改变
}
- (void)demo {
    //输出当前消息循环的模式
    NSLog(@"hello %@",[NSRunLoop currentRunLoop].currentMode);
}

二、子线程的消息循环

  • 主线程的消息循环默认开启,子线程的消息循环不会开启
  • 启动子线程的消息循环
    • [[NSRunLoop currentRunLoop] run];
  • *线程池的实现原理,开启线程后永不销毁,当需要让子线程执行新的方法,使用performSelector让指定的方法在指定的子线程上运行
- (void)viewDidLoad {
    [super viewDidLoad];
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(demo) userInfo:nil repeats:YES]; 
    //把定时器添加到当前线程消息循环中
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
    //消息循环是在一个指定的模式下运行的 默认的模式NSDefaultRunLoopMode,设置的输入事件也需要指定一个模式,消息循环的模式必须和输入事件的模式匹配才会执行    
    //UITrackingRunLoopMode  当滚动scrollView的时候,消息循环的模式自动改变
}
- (void)demo {
    //输出当前消息循环的模式
    NSLog(@"hello %@",[NSRunLoop currentRunLoop].currentMode);
}

三、GCD

  • 什么是GCD
    • 全称是Grand Central Dispatch
    • 纯C语言,提供了非常多强大的函数
  • GCD的优势
    • GCD是苹果公司为多核的并行运算提出的解决方案
    • GCD会自动利用更多的CPU内核(比如双核、四核)
    • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
    • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {   
//    //1 创建队列
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
//    //2 创建任务
//    dispatch_block_t task = ^{
//        NSLog(@"hello %@",[NSThread currentThread]);
//    };
//    //3 异步执行
//    dispatch_async(queue, task);    
    //简化用法
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"hello %@",[NSThread currentThread]);
    });   
}

异步下载网络图片

@interface ViewController ()
@property (nonatomic, strong) UIScrollView *scrollView;
@property (nonatomic, strong) UIImageView *imageView;
@end

@implementation ViewController
- (void)loadView {
    self.scrollView = [[UIScrollView alloc] initWithFrame:[UIScreen mainScreen].bounds];
    self.scrollView.backgroundColor = [UIColor whiteColor];
    self.view = self.scrollView;    
    self.imageView = [[UIImageView alloc] init];
    [self.scrollView addSubview:self.imageView];
}
- (void)viewDidLoad {
    [super viewDidLoad];
    //开启异步执行,下载网络图片
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSURL *url = [NSURL URLWithString:@"http://img02.tooopen.com/images/20141231/sy_78327074576.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *img = [UIImage imageWithData:data];        
        //回到主线程更新UI
        dispatch_sync(dispatch_get_main_queue(), ^{
            self.imageView.image = img;
            [self.imageView sizeToFit];            
            self.scrollView.contentSize = img.size;
        });
    });
}

3.1 任务和队列

  • GCD的两个核心
    • 任务:执行什么操作
    • 队列:用来存放任务
  • GCD使用的两个步骤
    • 创建任务:确定要做的事情
    • 将任务添加到队列中
      • GCD会自动将队列中的任务取出,放到对应的线程中执行
      • 任务的取出遵循队列的FIFO原则:先进先出,后进后出

3.2 执行任务的方式

  • GCD中有2个用来执行任务的函数
    • 同步的方式执行任务
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
queue:队列
block:任务
    • 异步的方式执行任务
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

3.3 队列的类型

  • GCD的队列可以分为2大类型
    • 并发队列(Concurrent Dispatch Queue)
      • 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
      • 并发功能只有在异步(dispatch_async)函数下才有效
    • 串行队列(Serial Dispatch Queue)
      • 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

3.4  总结

  • 同步和异步决定了要不要开启新的线程
    • 同步:在当前线程中执行任务,不具备开启新线程的能力
    • 异步:在新的线程中执行任务,具备开启新线程的能力
  • 并发和串行决定了任务的执行方式
    • 并发:多个任务并发(同时)执行
    • 串行:一个任务执行完毕后,再执行下一个任务

四、串行队列

  • 串行队列,同步执行
    • 不开线程,同步执行(在当前线程执行)
#define DISPATCH_QUEUE_SERIAL NULL
//串行队列
//dispatch_queue_t q = dispatch_queue_create("test", NULL);
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i < 10; i++) {
    //同步执行
    dispatch_sync(q, ^{
       NSLog(@"%@ -- %d",[NSThread currentThread],i);
    });
}
  • 串行队列,异步执行
    • 开一个线程,顺序执行    异步->开线程 
//只有一个线程,因为是串行队列,只有一个线程就可以按顺序执行队列中的所有任务
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 10; i++) {
        //异步执行
        dispatch_async(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });
    }
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self demo2];
}
//1 串行队列,同步执行   不开新线程, 任务是按顺序执行
- (void)demo1 {
    //串行队列
    dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_SERIAL);
    for (int i=0; i<10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"hello  %d   %@",i,[NSThread currentThread]);
        });
    }    
}
//2 串行队列,异步执行   开启新线程(1个),任务是有序执行
- (void)demo2 {
    //串行队列
    dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_SERIAL);
    for (int i=0; i<10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"hello  %d   %@",i,[NSThread currentThread]);
        });
    }
}

五、并行队列

  • 并行队列,异步执行
    • 开多个线程,异步执行
    • 开多个线程,异步执行,每次开启多少个线程是不固定的(线程数,不由我们控制),线程数是由gcd来决定的
dispatch_queue_t q = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; i++) {
        //异步执行
        dispatch_async(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });
    }
  • 并行队列,同步执行
    • 不开线程,顺序执行
//1 并行队列,同步执行  ----- 串行队列,同步执行   不开线程,顺序执行
- (void)demo1 {
    //并行队列
    dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_sync(queue, ^{
            NSLog(@"hello %d  %@",i,[NSThread currentThread]);
        });
    }
}
//2 并行队列,异步执行     开多个线程,无序执行
- (void)demo2 {
    //并行队列
    dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; i++) {
        dispatch_async(queue, ^{
            NSLog(@"hello %d  %@",i,[NSThread currentThread]);
        });
    }
}

六、主队列

  • 主队列,异步任务
    • 不开线程,同步执行
    • 主队列特点:如果主线程正在执行代码暂时不调度任务,等主线程执行结束后在执行任务
    • 主队列又叫 全局串行队列
  • 主队列,同步执行
    • 程序执行不出来(死锁)
    • 死锁的原因,当程序执行到下面这段代码的时候
      • 主队列:如果主线程正在执行代码,就不调度任务
      • 同步执行:如果第一个任务没有执行,就继续等待第一个任务执行完成,再执行下一个任务此时互相等待,程序无法往下执行(死锁)
dispatch_sync(q, ^{
            NSLog(@"%@ -- %d",[NSThread currentThread],i);
        });

七、 主队列和串行队列

  • 主队列和串行队列的区别
    • 串行队列:必须等待一个任务执行完成,再调度另一个任务
    • 主队列:以先进先出调度任务,如果主线程上有代码在执行,主队列不会调度任务
  • (主队列,同步执行)放入异步执行  解决死锁
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"全局队列,异步执行 %@",[NSThread currentThread]);
         //此时这行代码 在子线程中运行,同步执行不用等待主线程执行此同步执行的任务
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"主队列,同步执行 %@",[NSThread currentThread]);
        });
        NSLog(@"==");
  });
//1 主队列,异步执行   主线程,顺序执行
//主队列的特点:先执行完主线程上的代码,才会执行主队列中的任务
- (void)demo1 {
    for (int i = 0; i < 10; i++) {
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"hello %d  %@",i,[NSThread currentThread]);
        });
    } 
}
//2 主队列,同步执行  ---   主线程上执行才会死锁
//同步执行:会等着第一个任务执行完成,才会继续往后执行
- (void)demo2 {
    NSLog(@"begin");
    for (int i = 0; i < 10; i++) {
        //死锁
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"hello %d  %@",i,[NSThread currentThread]);
        });
    }
    NSLog(@"end");
}
//3 解决死锁的问题
- (void)demo3 {
    NSLog(@"begin");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 10; i++) {
            //死锁
            dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"hello %d  %@",i,[NSThread currentThread]);
            });
        }
    });
    NSLog(@"end");
}

八、全局队列

  • 全局队列本质就是并发队列
dispatch_get_global_queue(0,0);//优先级 服务质量 一般都是 0 0
  • 全局队列和并发队列的区别
    • 并发队列有名称,可以跟踪错误,全局队列没有
    • 在ARC中不需要考虑释放内存, dispatch_release(q);不允许调用。在MRC中需要手动释放内存,并发队列是create创建出来的 在MRC中见到create就要release,全局队列不需要release(只有一个)
    • 一般我们使用全局队列

九、同步任务

  • 同步任务的作用
    • 平时生活中很多事情都需要先后进行,比如公司月底,会先(计算工资),然后才能(发工资)
    • 在网络开发中,通常会把很多任务放在后台异步执行,有些任务会彼此"依赖"
    • appStore 验证密码--扣费--下载应用
  • 同步任务的特点:
    • 队列调度多个异步任务前,指定一个同步任务,让所有的异步任务都等待同步任务完成,这就是所谓的”依赖”关系
  • 演示代码  
    • 把验证密码的同步任务放到一个异步执行中去,因为验证密码比较耗时,此时验证密码这个同步任务也是在一个子线程中来完成的,不会阻塞UI
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    //appStore下载应用--输入密码--扣费--下载应用
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"输入密码 %@",[NSThread currentThread]);
        });       
        dispatch_sync(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"扣费 %@",[NSThread currentThread]);
//            [NSThread sleepForTimeInterval:1];
        });     
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"下载应用 %@",[NSThread currentThread]);
        });
    });
}

各种队列的执行效果

十、Barrier阻塞

  • 主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新
  • 适合于大规模的 I/O 操作
  • 当访问数据库或文件的时候,更新数据的时候不能和其他更新或读取的操作在同一时间执行,可以使用调度组不过有点复杂。可以使用dispatch_barrier_async解决。
@interface ViewController ()
@property (nonatomic, strong) NSMutableArray *photoList;
@end
@implementation ViewController
//懒加载
- (NSMutableArray *)photoList {
    if (_photoList == nil) {
        _photoList = [NSMutableArray array];
    }
    return _photoList;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    for (int i = 1; i<=1000; i++) {
        [self downloadImage:i];
    }
}
//模拟从网络上下载很多张图片,并且把下载完成的图片添加到mutableArray中
- (void)downloadImage:(int)index {
    //并发队列
    dispatch_queue_t queue = dispatch_queue_create("hm", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        //模拟下载图片
        NSString *fileName = [NSString stringWithFormat:@"%02d.jpg",index % 10 + 1];
        NSString *path = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
        UIImage *img = [UIImage imageWithContentsOfFile:path];             
        //等待队列中所有的任务执行完成,才会执行barrier中的代码
        dispatch_barrier_async(queue, ^{
            [self.photoList addObject:img];
            NSLog(@"保存图片 %@   %@",fileName,[NSThread currentThread]);
        });        
//        [self.photoList addObject:img];   
        NSLog(@"图片下载完成 %@  %@",fileName,[NSThread currentThread]);
    });
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@"%zd",self.photoList.count);
}
@end

十一、GCD的其它操作

  • 延迟操作
  • 一次性执行
  • 调度组

11.1 延时操作

延时操作
//延时操作
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
         
    });

•	dispatch_after的定义
dispatch_after(dispatch_time_t when,
dispatch_queue_t queue,
dispatch_block_t block);
 
•	dispatch_after的参数
参数1  dispatch_time_t when
多少纳秒之后执行
参数2  dispatch_queue_t queue
任务添加到那个队列
参数3  dispatch_block_t block
要执行的任务

11.2 一次性执行

  • 一次性执行是线程安全的
  • 可以使用一次性执行创建单例对象,效率比互斥锁高
- (void)viewDidLoad {
    [super viewDidLoad];   
    //1 延迟执行
//    dispatch_time_t when,   延迟多长时间 精度到纳秒
//    dispatch_queue_t queue, 队列
//    dispatch_block_t block  任务
//    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
//        
//        NSLog(@"task");
//    });   
    //2 一次性执行
//    for (int i = 0; i<20000; i++) {
//        static dispatch_once_t onceToken;
//        dispatch_once(&onceToken, ^{
//            NSLog(@"hello %@",[NSThread currentThread]);
//        });
//    }
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{       
        //当前线程上执行
        //一次性执行的原理 ,判断静态的全局变量的值 默认是0,如果执行完成后,设置为-1
        //once内部会判断变量的值,如果是0才执行
        static dispatch_once_t onceToken;
        NSLog(@"%zd",onceToken);
        dispatch_once(&onceToken, ^{
            NSLog(@"hello %@",[NSThread currentThread]);
        });
        NSLog(@"%zd",onceToken);
    });
}
+ (instancetype)sharedNetworkTools {
    static id instance = nil;
    //线程同步,保证线程安全
    @synchronized(self) {
        if (instance == nil) {
            instance = [[self alloc] init];
        }
    }
    return instance;
}
+ (instancetype)sharedNetworkToolsOnce {
    static id instance = nil;
    
    //dispatch_once 线程安全的
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (instance == nil) {
            instance = [[self alloc] init];
        }
    });
    return instance;
}
//获取加锁,创建的时间
- (void)demo1 {
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    for (int i=0; i<10000000; i++) {
        [NetworkTools sharedNetworkTools];
    }
    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
    NSLog(@"加锁: %f",end - start);
}
//获取once,创建的时间
- (void)demo2 {
    CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
    for (int i=0; i<10000000; i++) {
        [NetworkTools sharedNetworkToolsOnce];
    }
    CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
    NSLog(@"once: %f",end - start);
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值