[英]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.