Combine + SwiftUI中的最佳数据绑定实践? [英] Best data-binding practice in Combine + SwiftUI?
问题描述
在RxSwift中,将View Model
中的Driver
或Observable
绑定到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
之类的属性包装器以及诸如ObservableObject
,ObservedObject
之类的协议来自动处理绑定(自 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屋!