LoginSignup
16
8

More than 3 years have passed since last update.

DisposeBagに複数まとめて登録する時は`.disposed(by:)`を使うよりも`insert()`を使う方が良い

Last updated at Posted at 2019-09-18

TL;DR

hoge.subscribe(
    onNext: { ~ },
    onError: { ~ }
)
.disposed(by: disposeBag)
fuga.subscribe(
    onNext: { ~ },
    onError: { ~ }
)
.disposed(by: disposeBag)

disposeBag.insert(
    hoge.subscribe(
        onNext: { ~ },
        onError: { ~ }
    ),
    fuga.subscribe(
        onNext: { ~ },
        onError: { ~ }
    )
)

って書こうぜって話

DisposeBagとは

subscribeしているObservable等をまとめてdisposeしてくれる仕組みです。
DisposeBagオブジェクトが破棄されるタイミングで抱えているDisposableに対してdispose()をかけるようになっています。

参考記事はこちら

DisposeBagの実装

ソース
https://github.com/ReactiveX/RxSwift/blob/master/RxSwift/Disposables/DisposeBag.swift

まずはdisposed(by:)を見ていきます

DisposeBag.swift
extension Disposable {
    /// Adds `self` to `bag`
    ///
    /// - parameter bag: `DisposeBag` to add `self` to.
    public func disposed(by bag: DisposeBag) {
        bag.insert(self)
    }
}

はい、baginsertしてるだけですねw
ではこのinsertを見ていきます

DisposeBag.swift
private var _lock = SpinLock()

// state
private var _disposables = [Disposable]()
private var _isDisposed = false

/// Adds `disposable` to be disposed when dispose bag is being deinited.
///
/// - parameter disposable: Disposable to add.
public func insert(_ disposable: Disposable) {
    self._insert(disposable)?.dispose()
}

private func _insert(_ disposable: Disposable) -> Disposable? {
    self._lock.lock(); defer { self._lock.unlock() }
    if self._isDisposed {
        return disposable
    }
    self._disposables.append(disposable)
    return nil
}

これを見ると、
- bagが既に破棄されている場合にはinsertされたDisposableも破棄しちゃう
- bagが破棄されていないなら内部のキューに積む

といった動きをしていることが分かります。

また、DisposeBagには複数のDisposableをまとめて処理するためのメソッドも用意されています。

DisposeBag.swift
/// Convenience function allows a list of disposables to be gathered for disposal.
public func insert(_ disposables: Disposable...) {
    self.insert(disposables)
}

/// Convenience function allows an array of disposables to be gathered for disposal.
public func insert(_ disposables: [Disposable]) {
    self._lock.lock(); defer { self._lock.unlock() }
    if self._isDisposed {
        disposables.forEach { $0.dispose() }
    } else {
        self._disposables += disposables
    }
}

こちらも

  • bagが既に破棄されている場合にはinsertされたDisposableも破棄しちゃう
  • bagが破棄されていないなら内部のキューに積む

という点では一緒ですね。

つまり、

hoge.subscribe(
    onNext: { ~ },
    onError: { ~ }
)
.disposed(by: disposeBag)
fuga.subscribe(
    onNext: { ~ },
    onError: { ~ }
)
.disposed(by: disposeBag)

といったコードは

disposeBag.insert(
    hoge.subscribe(
        onNext: { ~ },
        onError: { ~ }
    )
)

disposeBag.insert(
    fuga.subscribe(
        onNext: { ~ },
        onError: { ~ }
    )
)

と置き換えられ、更に

disposeBag.insert(
    hoge.subscribe(
        onNext: { ~ },
        onError: { ~ }
    ),
    fuga.subscribe(
        onNext: { ~ },
        onError: { ~ }
    )
)

と置き換えられることが分かります。

見た目的にもここでまとめてdisposeBagに突っ込んでることが分かりやすくなりますし、
SpinLockについては詳しくないので断言はできませんが、
.disposed(by:)だと一々ロックかけたり解除したりを繰り返すのに対し、
まとめてinsertするなら1回のロックで済むのでパフォーマンス的にも良いんじゃないかなーと思ってます。
(あと、.繋ぎは改行するような規約の現場なら、シンプルに行数が減るのでLinterにも優しくなるはず)

結論

DisposeBag.insert()、積極的に使っていこう!

2021/01/26追記

RxSwift 6がリリースされ、DisposeBag.insertがFunction Builderに対応したようです。

disposeBag.insert {
    hoge.subscribe(
        onNext: { ~ },
        onError: { ~ }
    )
    fuga.subscribe(
        onNext: { ~ },
        onError: { ~ }
    )
}

カンマが不要になるので項目が増える際のdiffも減ってレビューもしやすくなりますね。

参考

https://github.com/ReactiveX/RxSwift/blob/master/RxSwift/Disposables/DisposeBag.swift
https://dev.to/freak4pc/what-s-new-in-rxswift-6-2nog#new-raw-disposebag-endraw-function-builder

16
8
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
16
8