【RxSwift】subscribe 時に省略系を用いると onNext と onDisposed で同じクロージャが処理されたように見える


環境

  • Xcode Version 12.3 (12C33)
  • RxSwift 5.0.1

行いたかったこと

  • あるイベントをきっかけに一度だけ読み込み処理を行いたい
  • self.subjectonNext(()) を通す

self.subject
    .take(1)
    .subscribe(onNext: { [weak self] _ in

         // 正常に動作する
         guard let self = self else { fatalError() }
         self.loadData()
    })
    .disposed(by: self.disposeBag)

省略すると...

  • 以下のように省略して書くと subscribeのクロージャが2回呼ばれる
self.subject
    .take(1)
    .subscribe { [weak self] _ in

         // 2回呼ばれてしまう
         guard let self = self else { fatalError() }
         self.loadData()
    }
    .disposed(by: self.disposeBag)

検証

実装

以下のような ViewController を作成

  • self.viewDidLoad() で購読処理を行う
  • onNext ボタンをタップすると onNext() を流す
  • onCompleted ボタンをタップすると onCompleted() を流す
  • onError ボタンをタップすると onError(TestError()) を流す
TestViewController.swift
import RxSwift

import UIKit


// MARK: - TestViewController

final class TestViewController: UIViewController {


    // MARK: - UIViewController

    override func viewDidLoad() {

        super.viewDidLoad()

        self.subject
            .subscribe { (value: Int) in

                print("ラベル省略: onNext")
            }
            .disposed(by: self.disposeBag)

        self.subject
            .subscribe(onNext: { _ in

                print("値省略: onNext")
            })
            .disposed(by: self.disposeBag)

        self.subject
            .subscribe { _ in

                print("全省略: onNext")
            }
            .disposed(by: self.disposeBag)
    }


    // MARK: - Private

    private struct TestError: Error {}

    @IBAction private func onNextButtonTapped(_ sender: Any) {

        print("***** onNext ******")
        self.subject.onNext(1)
    }

    @IBAction private func onCompletedButtonTapped(_ sender: Any) {

        print("***** onCompleted ******")
        self.subject.onCompleted()
    }

    @IBAction func onErrorButtonTapped(_ sender: Any) {

        print("***** onError ******")
        self.subject.onError(TestError())
    }

    private let subject = PublishSubject<Int>()

    private let disposeBag = DisposeBag()
}

試験

A: onNextを1回、onCompletedを1回流す
B: onNextを1回、onErrorを1回流す

結果

A: onCompleted を流した際に全省略のクロージャ内部が処理される

***** onNext ******
ラベル省略: onNext
値省略: onNext
全省略: onNext
***** onCompleted ******
全省略: onNext

B: onError を流した際に全省略のクロージャ内部が処理される

***** onNext ******
ラベル省略: onNext
値省略: onNext
全省略: onNext
***** onError ******
全省略: onNext

原因(2021/4/26 追記)

  • 引数を全て省略すると subscribe(onNext:onError:onCompleted:onDisposed:) ではなく subscribe(_ on:) として解釈されることが原因
  • subscribe(_ on:) では onNext onCompleted onErrorRxSwift.Event)それぞれのタイミングで同じクロージャが処理される
    • subscribe(onNext:onError:onCompleted:onDisposed:)onNextonDisposed で同じクロージャが処理されているように見えていた

結論

  • RxSwift でストリームの購読処理を行う際にラベル・引数ともに省略すると onNextonDisposed で同じクロージャが処理される
    • onNextonDisposed が同じクロージャの形式になるためか?
    • 詳細な原因は今のところ不明
    • メソッドが subscribe(_ on:) に変化してしまうため
  • .take(1) などで一度だけ処理する場合、一度 onNext イベントが流れると購読が disposed されるため onCompleted イベントが続けて流れるため 予期せず2回処理が走ってしまう
  • ラベル・値のどちらかを省略せず書くことで問題を回避可能
  • コード補完機能に従ってコーディングすると自然とラベルを省略しがちなので注意が必要