RxSwift с последним из странного поведения - PullRequest
0 голосов
/ 09 октября 2018

Сегодня я переносил свой код из ReactiveSwift в RxSwift и пришел к этому странному сценарию.

У меня есть Observable, составленный с оператором withLatestFrom внутри класса ViewModel, и он излучает тольков тестовой подписке, которую я сделал внутри инициализатора класса ViewModel во время его создания, но не в подписке, которую я сделал в ViewController.

Оператор withLatestFrom из этого Observable:принятие другого 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 и установить выходной фильтр отладчика на [!], чтобы игнорировать вывод garbagge, созданный 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.

Интересно, что если я снова вызову триггерную функцию latestFrom в Observable (buttonTapped()), то и подписка ViewModel, и подписка ViewController будут вызываться, как и ожидалось.

Кроме того, если я удаляю оператор withLatestFrom из наблюдаемой цепочки configPhrases и добавляю его только в toBePrinted Наблюдаемый, все работает как положено.

Это заставляет меня думать, что применение withLatestFrom Наблюдаемому, который уже применил оператор withLatestFrom, ошибка.

1 Ответ

0 голосов
/ 09 октября 2018

Я не вижу поведения, о котором вы говорите, возможно, потому что я представляю контроллер представления вместо того, чтобы использовать его в тестовом жгуте?

В любом случае, имейте в виду, что когда вы делаете a.withLatestFrom(b), если a испускает значение до того, как b испустит какие-либо значения, то оператор отфильтрует выброс.Может ли это быть вашей проблемой?

...