【发布时间】:2020-01-10 13:08:34
【问题描述】:
在 RxSwift 中,很容易将 Driver 或 View Model 中的 Observable 绑定到 ViewController 中的某个观察者(即 UILabel)。
我通常更喜欢构建一个管道,使用 从其他可观察对象创建的可观察对象,而不是“强制”推送值,例如通过 PublishSubject)。
让我们使用这个例子:从网络获取一些数据后更新UILabel
RxSwift + RxCocoa 示例
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 的项目中,您似乎可以保持相同的模式:
- 视图模型公开发布者
- 视图控制器将其 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)。
由于 (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 世界中,“旧的绑定方式”会被遗忘和取代吗?
【问题讨论】:
-
我不认为有任何内置的方法可以做你想做的事。 This 是某人制作的辅助函数,您可能会觉得有趣。
-
在 SwiftUI 中有两种处理异步数据的方法,或者可能有两种变体。您可以按照 Benjamin 的建议使用 onReceive 或将数据保存在类中并发送 objectWillChange 消息。我都用过,它们都很容易使用。我见过的 onReceive 的最大缺点是,由于视图的状态发生变化,它可能会受到正文被重新读取的影响,请参阅stackoverflow.com/questions/57796877/…,如果两个计时器都是 1 秒,则会出现问题-
标签: ios swift swiftui rx-swift combine