RxSwift withLatestFrom怪异行为

时间:2018-10-09 12:43:21

标签: swift reactive-programming rx-swift reactivex

今天,我将代码从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中运行它,并将调试器输出过滤器设置为[!],以忽略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"]

如您所见,观察者订阅不会在ViewController处调用,而只会在ViewModel处调用。

但是有趣的是,如果我再次调用Observable的latestFrom触发函数(buttonTapped()),则将按预期方式调用ViewModel订阅和ViewController订阅。

此外,如果我从withLatestFrom可观察链中删除configPhrases运算符,并且仅将它添加到toBePrinted可观察链中,则所有工作均按预期进行。

这使我认为将withLatestFrom应用于已应用withLatestFrom运算符的Observable会出错。

1 个答案:

答案 0 :(得分:0)

我没有看到您正在谈论的行为,可能是因为我展示了视图控制器而不是在测试工具中使用它?

无论如何,请记住,如果在a.withLatestFrom(b)发出任何值之前a发出值,则b会被运算符过滤掉。可能是您的问题吗?