Enr*_*rci 10 ios swift rx-swift swiftui combine
在RxSwift中,很容易将a Driver或Observablein 绑定到a View Model中的某个观察者ViewController(即UILabel)。
我通常更喜欢用其他可观察变量创建的可观察变量建立一个管道,而不是“强制”推送值(例如通过a PublishSubject)。
让我们使用此示例:从网络中获取一些数据后更新aUILabel
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!" }
}
}
Run Code Online (Sandbox Code Playgroud)
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)
}
}
Run Code Online (Sandbox Code Playgroud)
在基于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()
}
}
Run Code Online (Sandbox Code Playgroud)
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)
}
}
Run Code Online (Sandbox Code Playgroud)
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!
}
}
Run Code Online (Sandbox Code Playgroud)
我能想到的最接近的是订阅您的视图模型(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)
}
}
Run Code Online (Sandbox Code Playgroud)
struct ContentView: View {
@ObservedObject var viewModel = SwiftUIViewModel()
var body: some View {
Text(viewModel.stringValue)
}
}
Run Code Online (Sandbox Code Playgroud)
在这个没有UIViewController的新世界中,“旧的绑定方式”会被遗忘和取代吗?
我发现的一种优雅的方法是用 替换发布者上的错误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)
Run Code Online (Sandbox Code Playgroud)
我认为这里缺少的一点是你忘记了你的 SwiftUI 代码是功能性的。在 MVVM 范式中,我们将功能部分拆分到视图模型中,并将副作用保留在视图控制器中。使用 SwiftUI,副作用会被推到 UI 引擎本身的更高位置。
我还没有对 SwiftUI 搞砸太多,所以我不能说我理解所有的后果,但与 UIKit 不同,SwiftUI 代码不直接操作屏幕对象,而是创建一个结构,当传递给用户界面引擎。