iOS线程初探(四) GCD 和 NSOperation 小结

2023-11-20

参考资料:关于iOS多线程,看我就够了

GCD

在GCD中,有两个概念很重要,那就是任务和队列。

  • 任务 :其实就是你需要做的事情,一个Block而已。任务有两种执行方式:同步执行和异步执行。同步执行:会阻塞当前线程,直至该任务执行完成后当前线程才接触阻塞,继续执行下面的任务。异步执行:执行任务时不会阻塞当前线程,当前线程会继续往下执行(这里有个小问题,如果我在主线程中异步调用一个任务,既然不阻塞当前线程,那么这个任务还是主线程执行?)。
  • 队列:用于存放任务。一共有两种队列,串行队列和并行队列。串行队列中的任务按照FIFO的顺序被GCD取出来,一个一个按序执行。并行队列中的任务也按照FIFO的顺序被GCD取出来,但是执行时会被放到另外的线程执行。

创建队列

  • 主队列:这是一个特殊的串行队列,任何UI的操作都在主队列中。
dispatch_queue_t queue = dispatch_get_main_queue();
  • 创建自己的队列:创建队列时,第一个参数是标识符,用于标识自己的队列,第二个参数表示串行还是并行,传入DISPATCH_QUEUE_SERIAL或者NULL表示串行,传入DISPATCH_QUEUE_CONCURRENT表示并行。
//串行队列
dispatch_queue_t queue = dispatch_queue_create("MyQueue", NULL);
dispatch_queue_t queue = dispatch_queue_create("MyQueue", DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_t queue = dispatch_queue_create("MyQueue", DISPATCH_QUEUE_CONCURRENT);
  • 全局并行队列:只要是并行任务一般都加入到这个队列,这个队列是系统提供的并发队列。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  • 创建线程任务
    同步任务:会阻塞当前线程(SYNC)
dispatch_sync(queue, ^{
   //your code
   NSLog(@"%@", [NSThread currentThread]);
   });

异步任务:不会阻塞当前线程(ASYNC)

   dispatch_async(queue, ^{
   //your code
   NSLog(@"%@", [NSThread currentThread]);
   });
  • 队列组
    队列组可以将很多队列添加到一个组里,所有的任务完成后,队列组会通过一个方法通知我们。
//创建队列组
dispatch_group_t group = dispatch_group_create();
//创建队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//使用队列组方法只有异步方法
//执行3次循环
dispatch_group_async(group, queue, ^{
    for(int i = 0; i < 3; ++i)
        NSLog(@"%@", task 1);
});

//主队列中执行循环2次
dispatch_group_async(group, dispatch_get_main_queue(), ^{
    for(int i = 0; i < 2; ++i)
        NSLog(@"%@", task 2);
 });

//执行4次循环
dispatch_group_async(group, queue, ^{
    for(int i = 0; i < 4; ++i)
        NSLog(@"%@", task 3);
});

//3个线程任务完成后自动通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"Complete !");
});
  • 带阻塞队列的异步调用
   dispatch_barrier_async(_queue:dispatch_queue_t, _block:dispatch_block_t);

这个方法的重点是你传入的queue,当你传入的是通过DISPATCH_QUEUE_CONCURRENT参数自己创建的queue时,这个方法会阻塞这个queue(注意,是阻塞queue,而不是当前线程),直到这个queue中在_block之前的任务被执行完后才开始执行自己,自己执行完毕后,才取消这个queue的阻塞,使这个queue后面的任务得以被执行。但是如果你传入的是其它的队列,那么这个函数和dispatch_async没有区别了

dispatch_barrier_sync(_queue: dispatch_queue_t, _block:dispatch_block_t);

这个方法的使用和上面的一样,不同的是这个方法不仅阻塞队列,还会阻塞当前线程。如果你传入的是其它的queue,那么它就和dispatch_sync一样了。

NSOperation和NSOperationQueue

NSOpreation是Apple对GCD的再封装,完全面向对象。NSOperation和NSOpreationQueue分别对应GCD的任务和队列。
我们需要做的就是将任务封装到NSOperation对象中,再将此任务添加到NSOperationQueue对象中即可。具体操作往下看吧。

添加任务

需要说明的是,NSOperation只是一个抽象类,不能封装任务。我们用它的两个子类来封装任务,这两个子类分别是:NSInvocationOperation和NSBlockOperation。创建一个operation后,需要调用start方法来启动任务,它会默认在当前队列中同步执行。取消任务需要调用cancel方法。

  • NSInvocationOperation 需要传入一个方法名(任务)
//创建NSInvocationOperation对象
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
//执行任务
[operation start];
  • NSBlockOperation
//创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock: ^{
    NSLog(@"%@", [NSThread currentThread]);
}];

//开始任务
[operation start];

之前说过,这样封装的任务默认会在当前线程同步执行。但是NSBlockOperation还有个方法是addExecutionBlock:,通过这个方法可以给Operation添加多个执行Block。这样Operation中的任务会并发执行,它会在主线程和其它多个线程执行这些任务。

//创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock: ^{
    NSLog(@"%@", [NSThread currentThread]);
}];

//添加多个Block
for (int i = 0; i < 5; ++i)
{
    [operation addExecutionBlock: ^{
        NSLog(@"task %d: %@", i, [NSThread currentThread]);
    }];
}
//开始任务
[operation start];

注意:addExecutionBlock方法必须在start之前添加,否则会报错。

  • 自定义Operation
    除了上面的两种 Operation 以外,我们还可以自定义 Operation。自定义 Operation 需要继承 NSOperation 类,并实现其 main() 方法,因为在调用 start() 方法的时候,内部会调用 main() 方法完成相关逻辑。所以如果以上的两个类无法满足你的欲望的时候,你就需要自定义了。你想要实现什么功能都可以写在里面。除此之外,你还需要实现 cancel() 在内的各种方法。

创建队列

这里的队列分为主队列和其他队列。只要任务添加到队列,就会自动调用start方法。

  • 主队列
NSOperationQueue *queue = [NSOperationQueue mainQueue];
  • 其它队列
    其它队列与主队列通过一个类方法来获取的方式不同,通过初始化产生的队列就是其它队列。
    注意:其它队列的任务会在其它线程并行执行。
//创建一个非主队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//创建NSBlockOperation对象
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock: ^{
    NSLog(@"%@", [NSThread currentThread]);
}];

//添加多个Block
for(int i = 0; i < 5; ++i)
{
    [operation addExecutionBlock:^{
        NSLog(@"task %d : %@", i, [NSThread currentThread]);
    }];
}

//添加任务到队列中
[queue addOperation :operation];

NSOperationQueue中有一个参数maxConcurrentOperationCount,用它来控制并发数量,如果并发数量为1,那么就是串行,否则是并行。NSOperationQueue中还有一个添加任务的方法 addOperationWithBolck:(void(^)(void))block。

添加依赖

NSOperation中可以为任务添加依赖。比如A任务完成后才执行B任务,B任务完成后执行C任务。

//mission 1
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"task 1 : %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0f];
}];
//mission 2
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"task 2 : %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0f];
}];
//mission 3
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"task 3 : %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0f];
}];

//设置依赖
[operation2 addDependency:operation1];
[operation3 addDependency:operation2];

//创建队列并加入任务
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

注意别互相依赖,会导致死锁
可以使用removeDependency来解除依赖关系
依赖只与任务有关,与队列无关

其它方法

  • NSOperation

    BOOL executing; //判断任务是否正在执行
    BOOL finished; //判断任务是否完成
    void (^completionBlock)(void); //用来设置完成后需要执行的操作
    (void)cancel; //取消任务
    (void)waitUntilFinished; //阻塞当前线程直到此任务执行完毕

  • NSOperationQueue

    NSUInteger operationCount; //获取队列的任务数
    (void)cancelAllOperations; //取消队列中所有的任务
    (void)waitUntilAllOperationsAreFinished; //阻塞当前线程直到此队列中的所有任务执行完毕
    [queue setSuspended:YES]; // 暂停queue
    [queue setSuspended:NO]; // 继续queue

延时执行

  • perform
 //3秒后自动调用self的run方法,并传参数@"abc"
[self performSelector:@selector(run:) withObject:@"abc" afterDelay:3];
  • GCD
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
double delay = 3;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay *    NSEC_PER_SEC)), queue, ^{
    //your code , execute after 3sec
});
  • NSTimer
[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(run:) userInfo:@"abc", repeats:NO];

单例模式

标准实现方法

@interface Tool: NSObject <NSCopying>
+ (instancetype)shareTool;
@end

@implementation Tool

static id _instance;

+(instancetype)shareTool
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [[Tool alloc] init];
    });
    return _instance;
}

@end

从其它线程回到主线程

  • NSThread
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
  • GCD
dispatch_async(dispatch_get_main_queue(), ^{

});
  • NSOperationQueue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{

}];
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

iOS线程初探(四) GCD 和 NSOperation 小结 的相关文章

  • 图表无法在 Xcode 14 上编译

    我在图书馆中面临以下错误 Type 图表数据集 不符合协议 范围可替换集合 实例方法不可用 替换Subrange with 用于满足协议的要求 范围可替换集合 将其附加到扩展 ChartDataSet RangeReplaceableCol
  • 按下后退按钮时 SwiftUI 工具栏项目被剪裁

    我在 SwiftUI 中遇到了一种奇怪的行为 我似乎无法解决它 鉴于以下简单的示例应用程序 我遇到了这种行为 工具栏项目在初始运行时正确呈现 但导航离开并返回它会被剪切 重新创建此示例代码 内容视图 swift import SwiftUI
  • 从 UIImagePickerControllerReferenceURL 加载 UIImage

    我正在使用 UIImagePickerController 来允许用户从图像库中选择图像 然后我想在 sqlite 数据库中启动该文件的位置 以便稍后可以参考它 我一直在谷歌上搜索如何做到这一点 但我的结果相当简短 我知道我可以通过调用委托
  • 如何在 iPhone 应用程序中播放来自服务器的视频 URL

    实际上 我通过使用从服务器获取一个网址XMLParser 我想在我的应用程序中播放这个视频网址 现在我使用了 MPMoviePlayerController 但它不起作用 请帮忙 XML解析器 m id loadXMLByURL NSStr
  • 为什么 UIWebView 实例不调用scrollViewDidScroll?

    iOS 文档说 UIWeb视图 http developer apple com library ios documentation uikit reference UIWebView Class Reference Reference h
  • 将 firebase 框架添加到 ios 项目时出现链接器错误

    我一直在尝试将 firebase 框架添加到我的 ios 应用程序中 我按照这里的说明进行操作 https www firebase com docs ios quickstart html https www firebase com d
  • 横向 xib 显示为纵向

    我有一个视图控制器和单独的纵向和横向笔尖文件 旋转时 我加载相应的笔尖 方法 shouldAutorotateToInterfaceOrientation and willRotateToInterfaceOrientation 接到电话
  • 如何支持滑动删除具有组合布局的 UICollectionView 列表中的行?

    以前对于表视图 这是在UITableViewDataSource委托回调tableView commit forRowAt 相关 API 中是否有等效功能新的集合视图 https developer apple com documentat
  • VNFaceObservation BoundingBox 在纵向模式下不缩放

    作为参考 这源于一个问题视觉API 我正在努力使用Vision通过a检测图像中的人脸VNDetectFaceRectanglesRequest 它在确定图像中正确的人脸数量并提供boundingBox对于每张脸 我的麻烦是由于我UIImag
  • 使用 Core Data Swift 对 TableView 和行进行分区

    我在 sqlite 中有两个表 代码由 XCode Generation 生成 class Event NSManagedObject NSManaged var startDate NSDate NSManaged var details
  • 去除iOS输入阴影

    在 iOS Safari 5 上 我必须遵循输入元素 顶部内部阴影 我想删除顶部阴影 错误 webkit appearance不保存 目前的风格是 input border radius 15px border 1px dashed BBB
  • UIButton 未出现在 iPhone 5S 上

    总的来说 我对 iOS 开发和开发还很陌生 我一直在开发时间 记录保存应用程序 但遇到了一个奇怪的问题 在我的一个视图控制器上 我有一个 UITableView 每个单元格都是一个按钮 可通往不同的视图控制器 在第一个单元格上 用户应该能够
  • Swift 为 .toInt 提供Optional(3) 而不是 3

    尝试从字段中提取数字并继续获取可选 数字 而不是数字 IBOutlet weak var years UITextField IBAction func calculateYear sender AnyObject var a years
  • 如何使用 Unicode 十六进制值 (UTF-16) 在 Swift 中表达字符串

    我想在 Swift 中使用十六进制值编写 Unicode 字符串 我已阅读文档 https developer apple com library prerelease ios documentation Swift Conceptual
  • 在 Objective-C iPad 开发中发布

    我正在尝试发出 POST 请求 但我似乎无法弄清楚出了什么问题 我从服务器收到响应 但我的电子邮件 密码对似乎没有正确发送 读取 由服务器 它告诉我不存在这样的帐户 这是我的代码 它包含在一个函数中 当用户按下我创建的 登录 按钮时调用该函
  • Swift 3 的 NSLog 问题

    迁移到 Swift 3 后 当我尝试执行以下操作时出现错误 self publicDB save listRecord completionHandler record error gt Void in if let saveError e
  • 如果 UIGestureRecognizer 触发,如何取消按钮点击?

    更新 问题似乎是对另一个 GestureRecognizer 的依赖失败 请参阅此问题下面的评论和测试项目 在我的 iPhone 应用程序中 我有一个包含多个 UIButtons 作为子视图的视图 该视图还有一个 UITapGestureR
  • 在 xCode 6 中发现意外的 Mach-O 标头代码:1918975009

    导致错误的原因是什么 Found an unexpected Mach O header code 1918975009 in xCode 6 我将我的项目存档并作为ad hoc构建并且它构建 链接 存档很好但是当我在xCode组织者和选择
  • UIWebView stringByEvaluatingJavaScriptFromString 在后台

    在 iOS 应用程序中 我正在运行一个相当大的脚本UIWebView using stringByEvaluatingJavaScriptFromString 就 JavaScript 字符串的长度而言较大 调用 JavaScript 后会
  • 如何在 Swift 中解析蓝牙设备发送的浮点数?

    在我的 iOS 应用程序上 我需要解码蓝牙接收到的 Float 值 并从不同的设备 不是 iOS 获取 4 个字节 因此我需要一个 便携式 4 字节 Float 格式 目前发件人正在使用以下格式 数据编码 0xCCBBAAAEE 0xEE

随机推荐

  • Yii2框架(一)安装及目录结构

    如题 对 你要相信自己的眼睛 你没看错确实是Yii2框架 现在都2020年了 刚刚开始折腾Yii2框架难免有些过时 但是没办法 公司目前的项目是基于Yii2开发的 嘿 你说怎么这么巧 我还没用过Yii2框架 没办法 看手册呗 这玩意 万变不
  • Vue2运行报错SyntaxError: Cannot use import statement outside a module

    问题描述 像配置vue3那样配置vue config js的路径代理 代替src后 报错 语法错误 不能在模块外部使用导入语句 原因 模块语法一个是CommonJS module 一个是 ES6 module vue config js里的
  • Google Voice账号的具体保号教程

    Google Voice 官方号码回收规则 https www google com intl zh CN googlevoice program policies html 在9个月内你的Google Voice没拔打电话或接收短信 你的
  • Hbase使用shell命令报错大集锦:

    一 Hbase使用shell命令出现报错 PleaseHoldException Master is initializing 解决办法 0 保证hbase运行着的 1 删除HDFS里hbase文件夹 hdfs dfs rm r hbase
  • 蓝牙BLE连接设备报错onClientConnectionState() - status=133解决方法

    S905平台 Android 5 1 1 WIFI 蓝牙芯片AP6255 客户某蓝牙设备使用他们专用的APK连接不上 查logcat信息如下 D BluetoothAdapter 5097 stopLeScan D BluetoothAda
  • Arduino智能小车随笔(一)

    Arduino智能小车随笔 一 看到小孩的各种STEM课程很火 收费又老贵 就想着不如自己和小孩一起学着做个arduino智能小车 即能让女儿学点编程 又能省了智商税 一举两得 然而到了后面女儿基本上只帮我接线 arduBlock实在无法支
  • ZooKeeper踩坑

    一 下载安装包时要下载文件名中带有bin的安装包 否则会报错 找不到或无法加载主类 org apache zookeeper server quorum QuorumPeerMain Error contacting service 这是由
  • Alluxio集群环境搭建,救救孩子!

    写在前面 最近的一段时间内我会做HDFS Alluxio HBase相关的开发工作 我会继续学习并分享不限于这些组件的知识 正常来说 搭建个集群环境根本不需要专门写个文章 但是这次我必须写 顺带必须小吐槽一下 Alluxio官方文档搭建集群
  • python程序填空题 快乐的数字_Python习题之快乐的数字

    快乐的数字 描述 编写一个算法来确定一个数字是否 快乐 快乐的数字按照如下方式确定 从一个正整数开始 用其每位数的平方之和取代该数 并重复这个过程 直到最后数字要么收敛等于1且一直等于1 要么将无休止地循环下去且最终不会收敛等于1 能够最终
  • Spring Boot整合ElasticSearch

    一 ES客户端 ES提供多种不同的客户端 1 TransportClient ES提供的传统客户端 官方计划8 0版本删除此客户端 2 RestClient RestClient是官方推荐使用的 它包括两种 REST Low Level C
  • vs+qt新建ui项目

    新建项目入门参考教程 新建项目入门参考教程 手动调整UI及程序编写 手动调整UI及程序编写 Qss基础 Qt part 6 QSS Qt样式表 界面美化1 Qt part 7 QSS参考样式表 界面美化2 Qt part 8 QSS 按键菜
  • ArcGIS中KML/KMZ转为.shp文件

    kml kmz到 gt layer到 gt shp 1 打开ArcMap gt ArcToolbox 2 在ArcToolbox中选择 转换工具 gt 由KML转出 gt KML转图层 3 在 KML转图层 的弹出框中 选择并导入KML文件
  • numpy基本矩阵操作

    矩阵乘法 numpy当中常用的矩阵乘法有两种 numpy dot和numpy matmul 当对象是2D矩阵的时候 这两个函数都是进行最正常的矩阵乘法 import numpy as np a np array 1 2 3 4 b np a
  • java-ipfs-api.jar的食用方法

    引入java ipfs api jar 从仓库引入 在pom xml中添加仓库
  • 微信小程序实现车牌号键盘

  • Adobe软件还行吗?

    前段时间 美国政府 准备通过诉讼阻止Adobe去年公布的 以200亿美元收购Figma的交易 理由是此举属于反竞争行为 新闻一出 Adobe股价旋即下跌 但跌幅并不像2022年刚公布这笔交易时那么迅猛 看起来 相较于收购成功 投资者们反而希
  • gzip模块配置指令

    1 gzip指令 该指令用于开启或者关闭gzip功能 语法 gzip on off 默认值 gzip off 位置 http server location 注意只有该指令为打开状态 下面的指令才有效果 http gzip on 2 gzi
  • Python学习:random模块下的choices()函数详解

    1 random choice seq 函数 从非空序列中随机选取一个数据并返回 该序列可以是list tuple str set 举例 import random print random choice choice 结果 choice其
  • IP包头&ARP协议笔记

    一 IP包头分析 1 帧中的IP包头 从版本到可选项 其中2为帧头 注 1 IP包头最小长度 20字节 即可选项以前部分 IP包头长度是可变的 2 可选项最长可以是40个字节 故IP包头最长可以是60个字节 1 版本 4 说明是IPv4 2
  • iOS线程初探(四) GCD 和 NSOperation 小结

    参考资料 关于iOS多线程 看我就够了 GCD 在GCD中 有两个概念很重要 那就是任务和队列 任务 其实就是你需要做的事情 一个Block而已 任务有两种执行方式 同步执行和异步执行 同步执行 会阻塞当前线程 直至该任务执行完成后当前线程