在RxSwift中,将Driver
中的Observable
或View Model
绑定到ViewController
中的某个观察者(即UILabel
)非常容易。
我通常更喜欢用从其他可观察对象创建的可观察对象建立一个管道,而不是通过命令PublishSubject
“强制”推送值。
让我们使用这个示例:从网络中获取一些数据后更新UILabel
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的项目中,似乎可以保持相同的模式:
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依靠@Published
之类的属性包装器和ObservableObject
,ObservedObject
之类的协议来自动处理绑定(自 Xcode 11b7 起)。
由于(AFAIK)属性包装器无法“即时创建”,因此您无法使用相同的模式来重新创建上述示例。 以下无法编译
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!),并强制更新您的媒体资源,这根本感觉不对,而且反应迟钝。
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 的世界中,是否会忘记并替换“绑定的旧方法”?
答案 0 :(得分:3)
我发现的一种优雅方法是用Never
替换发布者上的错误,然后使用assign
(assign
仅在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)
答案 1 :(得分:2)
我认为这里遗漏的部分是您忘记了您的SwiftUI代码是功能。在MVVM范例中,我们将功能部分拆分为视图模型,并将副作用保留在视图控制器中。借助SwiftUI,副作用会进一步推向UI引擎本身。
我还没有对SwiftUI造成太多混乱,所以我不能说我理解所有的后果,但是与UIKit不同,SwiftUI代码不会直接操作屏幕对象,而是创建了一个结构来在操作时进行操作。传递给UI引擎。
答案 2 :(得分:0)
我最终做出了一些妥协。在viewModel中使用@Published
,但在SwiftUI View中进行订阅。
像这样:
final class SwiftUIViewModel: ObservableObject {
struct Output {
var dataPublisher: AnyPublisher<String, Never>
}
@Published var dataPublisher : String = "ggg"
func bind() -> Output {
let dataPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "https://www.google.it")!)
.map{ "Just for testing - \($0)"}
.replaceError(with: "An error occurred")
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
return Output(dataPublisher: dataPublisher)
}
}
和SwiftUI:
struct ContentView: View {
private var cancellableBag = Set<AnyCancellable>()
@ObservedObject var viewModel: SwiftUIViewModel
init(viewModel: SwiftUIViewModel) {
self.viewModel = viewModel
let bindStruct = viewModel.bind()
bindStruct.dataPublisher
.assign(to: \.dataPublisher, on: viewModel)
.store(in: &cancellableBag)
}
var body: some View {
VStack {
Text(self.viewModel.dataPublisher)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: SwiftUIViewModel())
}
}
答案 3 :(得分:0)
发布上一个答案后,请重新考虑所有内容并将订阅放入viewModel中 这可能会更好,因为所有的Combine代码都在viewModel中。
常规ViewModel协议:
protocol ViewModelProtocol {
associatedtype Output
func bind() -> Output
func bindProperties(_ output: Output)
}
extension ViewModelProtocol {
func setupBindings() {
let output = bind()
bindProperties(output)
}
}
ViewModel类:
final class SwiftUIViewModel: ViewModelProtocol, ObservableObject {
//Outputs
@Published var dataPublisher : String = "ggg"
//Inputs
private let someButtonPressedSubject = PassthroughSubject<Void, Never>()
func someButtonPressed() {
someButtonPressedSubject.send()
}
struct Output {
var dataPublisher: AnyPublisher<String, Never>
}
private var cancellableBag = Set<AnyCancellable>()
func bind() -> Output {
let dataPublisher = URLSession.shared.dataTaskPublisher(for: URL(string: "https://www.google.it")!)
.map{ "Just for testing - \($0)"}
.replaceError(with: "An error occurred")
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
return Output(dataPublisher: dataPublisher)
}
func bindProperties(_ output: Output) {
output.dataPublisher
.assign(to: \.dataPublisher, on: self)
.store(in: &cancellableBag)
}
}
SwiftUI视图:
struct ContentView: View {
@ObservedObject var viewModel: SwiftUIViewModel
init(viewModel: SwiftUIViewModel) {
self.viewModel = viewModel
viewModel.setupBindings()
}
var body: some View {
VStack {
Text(self.viewModel.dataPublisher)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewModel: SwiftUIViewModel())
}
}