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

el/2024/7/17 21:00:42

 一、消息循环

  • 什么是消息循环
    • 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];        //回到主线程更新UIdispatch_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);
}

 


http://www.ngui.cc/el/3419342.html

相关文章

ios基础篇(十三)—— 调度组、NSOperationQueue

一、调度组 有时候需要在多个异步任务都执行完成之后继续做某些事情&#xff0c;比如下载歌曲&#xff0c;等所有的歌曲都下载完毕之后 转到 主线程提示用户 //1 全局队列dispatch_queue_t queue dispatch_get_global_queue(0, 0);//2 调度组dispatch_group_t group dispatc…

ios基础篇(十五)—— SDWebImage

一、SDWebImage介绍 什么是SDWebImage iOS中著名的牛逼的网络图片处理框架包含的功能&#xff1a;图片下载、图片缓存、下载进度监听、gif处理等等用法极其简单&#xff0c;功能十分强大&#xff0c;大大提高了网络图片的处理效率国内超过90%的iOS项目都有它的影子项目地址 ht…

ios基础篇(十六)—— 网络之sockct

一、网络基本概念 客户端&#xff1a;应用 C/S B/S服务器&#xff1a;为客户端提供服务、数据、资源的机器请求&#xff1a;客户端向服务器索取数据响应&#xff1a;服务器对客户端的请求作出反应&#xff0c;一般是返回给客户端数据服务器 内网服务器外网服务器本地测试服…

时序图

一、时序图简介&#xff08;Brief introduction&#xff09; 时序图&#xff08;Sequence Diagram&#xff09;是显示对象之间交互的图&#xff0c;这些对象是按时间顺序排列的。 顺序图中显示的是参与交互的对象及其对象之间消息交互的顺序。 时序图中包括的建模元素主要有&a…

《Objective-C高级编程:iOS与OS X多线程和内存管理》 一 自动引用计数

一、自动引用计数 1.1 内存管理的思考方式 对象操作与objective-c 方法的对应对象操作Object-C 方法生成并持有对象alloc、new、copy、mutableCopy持有对象retain方法释放对象release方法废弃对象dealloc方法1.1.1 自己生成的对象&#xff0c;自己持有 使用以下名称开头的方…

《Objective-C高级编程:iOS与OS X多线程和内存管理》 一 Blocks模式GCD

一、Blocks摘要 Blocks&#xff1a;带有自动变量&#xff08;局部变量&#xff09;的匿名函数。 匿名函数&#xff1a;不带有名称的函数。 int func(int cout); //声明名称为func的函数 int result func(10); // 调用该函数&#xff0c;必须使用该函数的名称 // 若像下面这…

JXCategoryTitleView的使用

最近写项目&#xff0c;遇到了一个vc下面有多个vc 指示器可以点击切换&#xff0c;也可也左右滑动进行切换&#xff0c;解除了JXCategoryTitleView 感觉很好用&#xff1b; 一般要求主vc遵循JXCategoryViewDelegate、JXCategoryListContainerViewDelegate两个协议 子vc遵循JX…

基本编程能力练习

一、首先下载Java的编译环境并安装 JDK下载地址&#xff1a;https://www.oracle.com/java/technologies/javase-jdk15-downloads.html 选择适合自己系统的版本进行安装 这里我选择的是jdk-15_windows-x64_bin.exe 下载完成后直接运行安装&#xff0c;安装位置选择默认的C盘&am…

基本任务1.1Java语言基础

一、任务要求 完成基本数据类型的使用。完成基本运算符和表达式的使用。完成基本控制语句&#xff1a;判断、分支、循环等语句的使用。完成数组的使用。所有源代码必须加入行一级注释。 二、任务的理解 本次任务是让我们对Java编程中所遇到的基础知识进行学习和掌握&#xf…

基本任务1.1Java语言基础(任务挑战)

任务挑战&#xff1a; 1.编制一个Java application应用程序&#xff0c;把数组中已知的10个数字由小到大排序后输出。 编程前思考&#xff1a; 完成数组排序的方法有多种&#xff0c;其中有调用Arrays类中的sort方法直接对数组进行由小到大的排序&#xff0c;还可以采用冒泡排序…