Combine + SwiftUI中的最佳数据绑定实践? [英] Best data-binding practice in Combine + SwiftUI?

查看:390
本文介绍了Combine + SwiftUI中的最佳数据绑定实践?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在RxSwift中,将View Model中的DriverObservable绑定到ViewController(即UILabel)中的某些观察者非常容易.

In RxSwift it's pretty easy to bind a Driver or an Observable in a View Model to some observer in a ViewController (i.e. a UILabel).

我通常更喜欢建立一个由其他可观察对象创建的可观察对象的管道,而不是强制"推送值(例如通过PublishSubject).

I usually prefer to build a pipeline, with observables created from other observables, instead of "imperatively" pushing values, say via a PublishSubject).

让我们使用这个示例:从网络中获取一些数据后,更新UILabel

Let's use this example: update a UILabel after fetching some data from the network

final class RxViewModel {
    private var dataObservable: Observable<Data>

    let stringDriver: Driver<String>

    init() {
        let request = URLRequest(url: URL(string:"https://www.google.com")!)

        self.dataObservable = URLSession.shared
            .rx.data(request: request).asObservable()

        self.stringDriver = dataObservable
            .asDriver(onErrorJustReturn: Data())
            .map { _ in return "Network data received!" }
    }
}

final class RxViewController: UIViewController {
    private let disposeBag = DisposeBag()
    let rxViewModel = RxViewModel()

    @IBOutlet weak var rxLabel: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        rxViewModel.stringDriver.drive(rxLabel.rx.text).disposed(by: disposeBag)
    }
}


组合+ UIKit示例

在基于UIKit的项目中,似乎可以保持相同的模式:


Combine + UIKit example

In a UIKit-based project it seems like you can keep the same pattern:

  • 视图模型公开发布者
  • 视图控制器将其UI元素绑定到这些发布者
final class CombineViewModel: ObservableObject {
    private var dataPublisher: AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>
    var stringPublisher: AnyPublisher<String, Never>

    init() {
        self.dataPublisher = URLSession.shared
            .dataTaskPublisher(for: URL(string: "https://www.google.it")!)
            .eraseToAnyPublisher()

        self.stringPublisher = dataPublisher
            .map { (_, _) in return "Network data received!" }
            .replaceError(with: "Oh no, error!")
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
}

final class CombineViewController: UIViewController {
    private var cancellableBag = Set<AnyCancellable>()
    let combineViewModel = CombineViewModel()

    @IBOutlet weak var label: UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

        combineViewModel.stringPublisher
            .flatMap { Just($0) }
            .assign(to: \.text, on: self.label)
            .store(in: &cancellableBag)
    }
}


SwiftUI怎么样?

SwiftUI依赖于诸如@Published之类的属性包装器以及诸如ObservableObjectObservedObject之类的协议来自动处理绑定(自 Xcode 11b7 起).


What about SwiftUI?

SwiftUI relies on property wrappers like @Published and protocols like ObservableObject, ObservedObject to automagically take care of bindings (As of Xcode 11b7).

由于(AFAIK)属性包装器无法即时创建",因此您无法使用相同的模式来重新创建上述示例. 以下无法编译

Since (AFAIK) property wrappers cannot be "created on the fly", there's no way you can re-create the example above using to the same pattern. The following does not compile

final class WrongViewModel: ObservableObject {
    private var dataPublisher: AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>
    @Published var stringValue: String

    init() {
        self.dataPublisher = URLSession.shared
            .dataTaskPublisher(for: URL(string: "https://www.google.it")!)
            .eraseToAnyPublisher()

        self.stringValue = dataPublisher.map { ... }. ??? <--- WRONG!
    }
}

我能想到的最接近的是订阅您的视图模型(UGH!),并强制更新您的媒体资源,这根本感觉不对,而且反应迟钝.

The closest I could come up with is subscribing in your view model (UGH!) and imperatively update your property, which does not feel right and reactive at all.

final class SwiftUIViewModel: ObservableObject {
    private var cancellableBag = Set<AnyCancellable>()
    private var dataPublisher: AnyPublisher<URLSession.DataTaskPublisher.Output, URLSession.DataTaskPublisher.Failure>

    @Published var stringValue: String = ""

    init() {
        self.dataPublisher = URLSession.shared
            .dataTaskPublisher(for: URL(string: "https://www.google.it")!)
            .eraseToAnyPublisher()

        dataPublisher
            .receive(on: DispatchQueue.main)
            .sink(receiveCompletion: {_ in }) { (_, _) in
            self.stringValue = "Network data received!"
        }.store(in: &cancellableBag)
    }
}

struct ContentView: View {
    @ObservedObject var viewModel = SwiftUIViewModel()

    var body: some View {
        Text(viewModel.stringValue)
    }
}

在这个新的无 UIViewController-less 的世界中,是旧的绑定方法"被遗忘和取代了吗?

Is the "old way of doing bindings" to be forgotten and replaced, in this new UIViewController-less world?

推荐答案

我发现的一种优雅方法是用Never替换发布者上的错误,然后使用assign(assign仅在).

An elegant way I found is to replace the error on the publisher with Never and to then use assign (assign only works if Failure == Never).

以您的情况...

dataPublisher
    .receive(on: DispatchQueue.main)
    .map { _ in "Data received" } //for the sake of the demo
    .replaceError(with: "An error occurred") //this sets Failure to Never
    .assign(to: \.stringValue, on: self)
    .store(in: &cancellableBag)

这篇关于Combine + SwiftUI中的最佳数据绑定实践?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆