Swift Combine — zip和combineLatest的理解与使用

2024-06-22 15:28

本文主要是介绍Swift Combine — zip和combineLatest的理解与使用,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Publisher 上还有一些其他的操作,比如 zipcombineLatest,能让我们在时序上对控制多个 Publisher 的结果进行类似 andor 的合并,它们在构建复杂 Publisher 逻辑时也十分有用。

zip

Publisher 中的 zipSequencezip 相类似:它会把两个 (或多个) Publisher 事件序列中在同一 index 位置上的值进行合并,也就是说,Publisher1 中的第一个事件和 Publisher2 中的第一个事件结对合并,Publisher1 中的第二个事件和 Publisher2 中的第二个事件合并,以此类推:
在这里插入图片描述
比如上图中:
Publisher1 中第一个位置值为 1,在等待到 Publisher2 的第一个值 A 到来时,两者进行合并,在输出的 Publisher 中形成 (1, A)。
两个 Publisher 第二个位置的 2 和 B 也同理。
在 Publisher2 中 C 和 D 连续发生时,由于 Publisher1 里对应序列位置的事件还没有发生,因此并不会输出在最终的 Publisher 中,直到 Publisher1 里的 3 和 4 被发布。
最后 5 在 Publisher2 里并没有对应位置的 Output 事件,因此它被最终的 Publisher 忽略了。

下面的代码全完验证了上面的结论:

import SwiftUI
import Combineclass ZipViewModel: ObservableObject {private var cancellable = Set<AnyCancellable>()let numbersPub = PassthroughSubject<Int, Never>()let lettersPub = PassthroughSubject<String, Never>()@Published var receivedValues: [(Int, String)] = []func zipSample() {numbersPub.zip(lettersPub).sink(receiveValue: { value inprint("Received value: \(value)")self.receivedValues.append(value)}).store(in: &cancellable)}func sendNumber(_ number: Int) {print("Send number: \(number)")numbersPub.send(number)}func sendLetter(_ letter: String) {print("Send letter: \(letter)")lettersPub.send(letter)}
}struct ZipDemo: View {@StateObject private var viewModel = ZipViewModel()@State private var number: String = ""@State private var letter: String = ""var body: some View {VStack {VStack {TextField("Enter number...", text: $number)TextField("Enter letter...", text: $letter)}.textFieldStyle(.roundedBorder).padding()HStack {Button("Send number") {if let intNumber = Int(number) {viewModel.sendNumber(intNumber)}}.frame(maxWidth: .infinity)Button("Send letter") {if !letter.isEmpty {viewModel.sendLetter(letter)}}.frame(maxWidth: .infinity)}.buttonStyle(BorderedProminentButtonStyle())}.onAppear {viewModel.zipSample()}}
}

打印结果如下:

Send number: 1
Send letter: A
Received value: (1, "A")
Send number: 2
Send letter: B
Received value: (2, "B")
Send letter: C
Send letter: D
Send number: 3
Received value: (3, "C")
Send number: 4
Received value: (4, "D")
Send number: 5

在这里插入图片描述
zip 在时序语义上更接近于“当…且…”,当 Publisher1 发布值,且 Publisher2 发布值时,将两个值合并,作为新的事件发布出去。在实践中,zip 经常被用在合并多个异步事件的结果,比如同时发出了多个网络请求,希望在它们全部完成的时候把结果合并在一起。

combineLatest

zip 相反,combineLatest的语义接近于“当…或…”,当 Publisher1 发布值,或者 Publisher2 发布值时,将两个值合并,作为新的事件发布出去。
不论是哪个 Publisher,只要发生了新的事件,combineLatest 就把新发生的事件值和另一个 Publisher 中当前的最新值合并。

combineLatest 操作的图示如下:
在这里插入图片描述
上图中,除了首个元素 1 以外,其余的每个事件值,不论是从哪个 Publisher 发出的,都会被用来进行组合和输出。
比如在 Publisher2 输出 A 时,combineLatest 将使用这个 A 和 Publisher1 的当前最终值1 组合出结果1A。同样,随后 Publisher1 发布的 2 也和 Publisher2 的最终值 A 组合得到 2A。这个过程持续下去,直到两个 Publisher 都结束。

下面的代码验证了这一结论:

import SwiftUI
import Combineclass CombineLatestViewModel: ObservableObject {private var cancellable = Set<AnyCancellable>()let numbersPub = PassthroughSubject<Int, Never>()let lettersPub = PassthroughSubject<String, Never>()@Published var receivedValues: [(Int, String)] = []func zipSample() {numbersPub.combineLatest(lettersPub).sink(receiveValue: { value inprint("Received value: \(value)")self.receivedValues.append(value)}).store(in: &cancellable)}func sendNumber(_ number: Int) {print("Send number: \(number)")numbersPub.send(number)}func sendLetter(_ letter: String) {print("Send letter: \(letter)")lettersPub.send(letter)}
}struct CombineLatestDemo: View {@StateObject private var viewModel = CombineLatestViewModel()@State private var number: String = ""@State private var letter: String = ""var body: some View {VStack {VStack {TextField("Enter number...", text: $number)TextField("Enter letter...", text: $letter)}.textFieldStyle(.roundedBorder).padding()HStack {Button("Send number") {if let intNumber = Int(number) {viewModel.sendNumber(intNumber)}}.frame(maxWidth: .infinity)Button("Send letter") {if !letter.isEmpty {viewModel.sendLetter(letter)}}.frame(maxWidth: .infinity)}.buttonStyle(BorderedProminentButtonStyle())}.onAppear {viewModel.zipSample()}}
}

打印结果为:

Send number: 1
Send letter: A
Received value: (1, "A")
Send number: 2
Received value: (2, "A")
Send letter: B
Received value: (2, "B")
Send letter: C
Received value: (2, "C")
Send letter: D
Received value: (2, "D")
Send number: 3
Received value: (3, "D")
Send number: 4
Received value: (4, "D")
Send number: 5
Received value: (5, "D")

在这里插入图片描述

在实践中,combineLatest 被用来处理多个可变状态,在其中某一个状态发生变化时,获取这些全部状态的最新值。比如你的 UI 上有多个 TextField,你可能想要在其中某一个值变动时获取到所有 TextField 中的值并对它们进行检查,比如说用户注册界面。

写在最后

对于 zipcombineLatest,它们有一个共同特点,那就是结合后的新 Publisher 所发出的数据是元组类型。对这两种操作,一种常见的模式是将结果的发出多元组数据的 Publisher 沿着响应链继续传递,使用我们之前看到过的各类 Operator 来获取能实际驱动 UI 和 app 状态的 Publisher

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

这篇关于Swift Combine — zip和combineLatest的理解与使用的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1084671

相关文章

SpringBoot使用GZIP压缩反回数据问题

《SpringBoot使用GZIP压缩反回数据问题》:本文主要介绍SpringBoot使用GZIP压缩反回数据问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录SpringBoot使用GZIP压缩反回数据1、初识gzip2、gzip是什么,可以干什么?3、Spr

Spring Boot 集成 Quartz并使用Cron 表达式实现定时任务

《SpringBoot集成Quartz并使用Cron表达式实现定时任务》本篇文章介绍了如何在SpringBoot中集成Quartz进行定时任务调度,并通过Cron表达式控制任务... 目录前言1. 添加 Quartz 依赖2. 创建 Quartz 任务3. 配置 Quartz 任务调度4. 启动 Sprin

springboot上传zip包并解压至服务器nginx目录方式

《springboot上传zip包并解压至服务器nginx目录方式》:本文主要介绍springboot上传zip包并解压至服务器nginx目录方式,具有很好的参考价值,希望对大家有所帮助,如有错误... 目录springboot上传zip包并解压至服务器nginx目录1.首先需要引入zip相关jar包2.然

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

Java使用SLF4J记录不同级别日志的示例详解

《Java使用SLF4J记录不同级别日志的示例详解》SLF4J是一个简单的日志门面,它允许在运行时选择不同的日志实现,这篇文章主要为大家详细介绍了如何使用SLF4J记录不同级别日志,感兴趣的可以了解下... 目录一、SLF4J简介二、添加依赖三、配置Logback四、记录不同级别的日志五、总结一、SLF4J

使用Python实现一个优雅的异步定时器

《使用Python实现一个优雅的异步定时器》在Python中实现定时器功能是一个常见需求,尤其是在需要周期性执行任务的场景下,本文给大家介绍了基于asyncio和threading模块,可扩展的异步定... 目录需求背景代码1. 单例事件循环的实现2. 事件循环的运行与关闭3. 定时器核心逻辑4. 启动与停

如何使用Nginx配置将80端口重定向到443端口

《如何使用Nginx配置将80端口重定向到443端口》这篇文章主要为大家详细介绍了如何将Nginx配置为将HTTP(80端口)请求重定向到HTTPS(443端口),文中的示例代码讲解详细,有需要的小伙... 目录1. 创建或编辑Nginx配置文件2. 配置HTTP重定向到HTTPS3. 配置HTTPS服务器

Java使用ANTLR4对Lua脚本语法校验详解

《Java使用ANTLR4对Lua脚本语法校验详解》ANTLR是一个强大的解析器生成器,用于读取、处理、执行或翻译结构化文本或二进制文件,下面就跟随小编一起看看Java如何使用ANTLR4对Lua脚本... 目录什么是ANTLR?第一个例子ANTLR4 的工作流程Lua脚本语法校验准备一个Lua Gramm

Java Optional的使用技巧与最佳实践

《JavaOptional的使用技巧与最佳实践》在Java中,Optional是用于优雅处理null的容器类,其核心目标是显式提醒开发者处理空值场景,避免NullPointerExce... 目录一、Optional 的核心用途二、使用技巧与最佳实践三、常见误区与反模式四、替代方案与扩展五、总结在 Java

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析