簡體   English   中英

Swift 將運算符與 RxSwift 框架中的 `withLatestFrom` 等功能相結合

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

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM