今天,我将代码从ReactiveSwift迁移到RxSwift,并来到了这个奇怪的场景。
我在Observable
类中有一个由withLatestFrom
运算符组成的ViewModel
,它仅在构成它时在ViewModel
类的初始值设定项内进行的测试订阅上发出,而在我在ViewController
中进行的订阅。
此Observable中的withLatestFrom
运算符正在接受另一个Observable
,该withLatestFrom
也已由[!]
运算符组成为其参数。
// emit phrases when viewDidLoad emits
let thePhrases = self.viewDidLoadSubject.withLatestFrom(self.configureWithPhrasesSubject)
// This is the problematic Observable
let printThePhrases = self.buttonTappedSubject.withLatestFrom(thePhrases)
这是我用来展示这种怪异行为的代码,您可以在XCode中运行它,并将调试器输出过滤器设置为
ViewController
,以忽略Simulator的垃圾输出:import UIKit
import RxSwift
public final class RxTestViewModel {
public init() {
// emit configuredWithPhrases when viewDidLoad emits
let configPhrases = self.viewDidLoadSubject
.withLatestFrom(self.configureWithPhrasesSubject)
.filter { $0 != nil }
.map { $0! }
// Show phrases to be printed on viewDidLoad
self.toBePrinted = configPhrases.asObservable()
_ = self.toBePrinted.subscribe(onNext: {
print("[!][\(Thread.current)] -- ViewModel.toBePrinted.onNext -> \($0)")
})
// Print first phrase whenever buttonTapped() is called
self.printSomething = self.buttonTappedSubject
.withLatestFrom(self.configureWithPhrasesSubject
.filter { $0 != nil }
.map { $0! })
_ = self.printSomething.subscribe(onNext: {
print("[!][\(Thread.current)] -- ViewModel.printSomething.onNext -> \($0)")
})
}
// MARK: Inputs
private let configureWithPhrasesSubject = BehaviorSubject<[String]?>(value: nil)
public func configureWith(phrases: [String]) {
print("[!][\(Thread.current)] -- ViewModel.configureWith")
self.configureWithPhrasesSubject.on(.next(phrases))
}
private let viewDidLoadSubject = PublishSubject<Void>()
public func viewDidLoad() {
print("[!][\(Thread.current)] -- ViewModel.viewDidLoad")
self.viewDidLoadSubject.on(.next( () ))
}
private let buttonTappedSubject = PublishSubject<Void>()
public func buttonTapped() {
print("[!][\(Thread.current)] -- ViewModel.buttonTapped")
self.buttonTappedSubject.on(.next( () ))
}
// MARK: Outputs
public let printSomething: Observable<[String]>
public let toBePrinted: Observable<[String]>
}
public final class RxTestViewController: UIViewController {
private let button: UIButton = UIButton()
private let viewModel: RxTestViewModel = RxTestViewModel()
public static func instantiate() -> RxTestViewController {
let vc = RxTestViewController()
vc.viewModel.configureWith(phrases: ["First phrase", "Second phrase", "Third phrase"])
return vc
}
}
extension RxTestViewController {
public override func viewDidLoad() {
super.viewDidLoad()
self.setupButton()
self.setupViewModel()
self.viewModel.viewDidLoad()
}
}
extension RxTestViewController {
private func setupViewModel() {
_ = self.viewModel.toBePrinted
.subscribeOn(ConcurrentMainScheduler.instance)
.subscribe(onNext: {
print("[!][\(Thread.current)] -- RxTestViewController.toBePrinted.onNext -> \($0)")
self.viewModel.buttonTapped()
})
_ = self.viewModel.printSomething
.subscribeOn(ConcurrentMainScheduler.instance)
.subscribe(onNext: {
print("[!][\(Thread.current)] -- RxTestViewController.printSomething.onNext -> \($0)")
})
}
}
extension RxTestViewController {
private func setupButton() {
// Add to view
self.view.addSubview(self.button)
// Button config
self.button.setTitle("CLICK ME", for: .normal)
self.button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
// Auto-layout
self.button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
self.button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
self.button.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)])
}
@objc
private func buttonTapped() {
self.viewModel.buttonTapped()
}
}
预期结果应为:
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- ViewModel.configureWith
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- ViewModel.viewDidLoad
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- ViewModel.toBePrinted.onNext -> ["First phrase", "Second phrase", "Third phrase"]
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- RxTestViewController.toBePrinted.onNext -> ["First phrase", "Second phrase", "Third phrase"]
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- ViewModel.buttonTapped
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- ViewModel.printSomething.onNext -> ["First phrase", "Second phrase", "Third phrase"]
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- RxTestViewController.printSomething.onNext -> ["First phrase", "Second phrase", "Third phrase"]
但是我得到了:
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- ViewModel.configureWith
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- ViewModel.viewDidLoad
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- ViewModel.toBePrinted.onNext -> ["First phrase", "Second phrase", "Third phrase"]
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- RxTestViewController.toBePrinted.onNext -> ["First phrase", "Second phrase", "Third phrase"]
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- ViewModel.buttonTapped
[!][<NSThread: 0x600001fee280>{number = 1, name = main}] -- ViewModel.printSomething.onNext -> ["First phrase", "Second phrase", "Third phrase"]
如您所见,观察者订阅不是在
ViewModel
调用的,而是在latestFrom
调用的。但是有趣的是,如果我再次调用Observable的
buttonTapped()
触发函数(withLatestFrom
),则将按预期方式调用ViewModel订阅和ViewController订阅。另外,如果我从
configPhrases
可观察链中删除toBePrinted
运算符,并且仅将其添加到withLatestFrom
可观察链中,则所有工作均按预期进行。这使我认为将
withLatestFrom
应用于已经应用运算符的Observable会出错。 最佳答案
我没有看到您正在谈论的行为,可能是因为我提供了视图控制器而不是在测试工具中使用它?
无论如何,请记住,当您执行a.withLatestFrom(b)
时,如果a
在b
发出任何值之前发出一个值,则运算符将过滤掉该发出。那可能是你的问题吗?