2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2021-02-24

環境

  • 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回処理が走ってしまう
  • ラベル・値のどちらかを省略せず書くことで問題を回避可能
  • コード補完機能に従ってコーディングすると自然とラベルを省略しがちなので注意が必要
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?