[英]Swift Combine operator with same functionality like `withLatestFrom` in the RxSwift Framework
我正在开发一个采用 MVVM 模式的 iOS 应用程序,使用 SwiftUI 设计视图,并使用 Swift 组合,以便将我的视图与其各自的 ViewModel 粘合在一起。 在我的一个 ViewModel 中,我为按钮按下创建了一个Publisher
(类型Void
),为TextField
的内容创建了另一个(类型String
)。 我希望能够在我的 ViewModel 中组合两个发布者,这样组合的发布者只在按钮发布者发出事件时发出事件,同时从字符串发布者那里获取最新事件,所以我可以对TextField
进行某种评估数据,每次用户按下按钮。 所以我的虚拟机看起来像这样:
import Combine
import Foundation
public class MyViewModel: ObservableObject {
@Published var textFieldContent: String? = nil
@Published var buttonPressed: ()
init() {
// Combine `$textFieldContent` and `$buttonPressed` for evaulation of textFieldContent upon every button press...
}
}
两个发布者都在使用 SwiftUI 的数据,所以我将省略这部分,让我们假设两个发布者随着时间的推移都会收到一些数据。
来自 RxSwift 框架,我的 goto 解决方案将是withLatestFrom运算符来组合两个可观察对象。 但是,在“组合来自多个发布者的元素”部分中深入研究发布者的 Apple 文档时,我找不到类似的东西,所以我希望目前缺少这种运算符。
所以我的问题是:是否可以使用 Combine Framework 的现有 operator-API 最终获得与withLatestFrom
相同的行为?
有一个内置的操作符听起来很棒,但是您可以从已有的操作符中构造相同的行为,如果这是您经常做的事情,很容易从现有的操作符中创建自定义操作符。
在这种情况下的想法是使用combineLatest
以及诸如removeDuplicates
类的运算符,以防止值在管道中传递,除非按钮已发出新值。 例如(这只是操场上的一个测试):
var storage = Set<AnyCancellable>()
var button = PassthroughSubject<Void, Never>()
func pressTheButton() { button.send() }
var text = PassthroughSubject<String, Never>()
var textValue = ""
let letters = (97...122).map({String(UnicodeScalar($0))})
func typeSomeText() { textValue += letters.randomElement()!; text.send(textValue)}
button.map {_ in Date()}.combineLatest(text)
.removeDuplicates {
$0.0 == $1.0
}
.map {$0.1}
.sink { print($0)}.store(in:&storage)
typeSomeText()
typeSomeText()
typeSomeText()
pressTheButton()
typeSomeText()
typeSomeText()
pressTheButton()
output 是"zed"
和"zedaf"
等两个随机字符串。 关键是每次我们调用typeSomeText
时,文本都会沿着管道发送,但除非我们调用pressTheButton
,否则我们不会在管道末端收到文本。
这似乎是你所追求的那种东西。
你会注意到我完全忽略了按钮发送的值是什么。 (在我的示例中,无论如何它只是一个 void。)如果该值很重要,则更改初始 map 以将该值包含为元组的一部分,然后删除元组的 Date 部分:
button.map {value in (value:value, date:Date())}.combineLatest(text)
.removeDuplicates {
$0.0.date == $1.0.date
}
.map {($0.value, $1)}
.map {$0.1}
.sink { print($0)}.store(in:&storage)
这里的重点是.map {($0.value, $1)}
行之后到达的内容与withLatestFrom
将产生的内容完全相同:两个发布者的最新值的元组。
随着@matt answer的改进,这更方便withLatestFrom
,它在原始 stream 中触发相同的事件
更新:修复了 14.5 之前的 iOS 版本中的 combineLatest 问题
extension Publisher {
func withLatestFrom<P>(
_ other: P
) -> AnyPublisher<(Self.Output, P.Output), Failure> where P: Publisher, Self.Failure == P.Failure {
let other = other
// Note: Do not use `.map(Optional.some)` and `.prepend(nil)`.
// There is a bug in iOS versions prior 14.5 in `.combineLatest`. If P.Output itself is Optional.
// In this case prepended `Optional.some(nil)` will become just `nil` after `combineLatest`.
.map { (value: $0, ()) }
.prepend((value: nil, ()))
return map { (value: $0, token: UUID()) }
.combineLatest(other)
.removeDuplicates(by: { (old, new) in
let lhs = old.0, rhs = new.0
return lhs.token == rhs.token
})
.map { ($0.value, $1.value) }
.compactMap { (left, right) in
right.map { (left, $0) }
}
.eraseToAnyPublisher()
}
}
一种非答案,但你可以这样做:
buttonTapped.sink { [unowned self] in
print(textFieldContent)
}
这段代码相当明显,不需要知道withLatestFrom
是什么意思,尽管有必须捕获self
的问题。
我想知道这是否是苹果工程师没有将withLatestFrom
添加到核心组合框架的原因。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.