The Pragmatic Ball boy

iOSを中心にやってる万年球拾いの老害エンジニアメモ

RxSwiftのVariableとBehaviorRelayとBehaviorSubjectの違い

動機

RxSwiftのVariableがdeprecatedになったということで、その代わりとしてBehaviorRelayに置き換えようと思ったときに、BehaviorRelayに単純に置き換えてよいのか?BehaviorSubjectもあるけどこっちはどうなんだっけ?という視点で調べてみました。

結論

単純にVariableをBehaviorRelayに置き換えて良い。

ただVariableのcompletedをトリガーになにかしていたら変更する必要がある。

Variableのvalueをmutateするようなことをしてたらちょっと影響ある。

詳細

Variable, BehaviorRelayのソースを見て違いを見ていきます。 (RxSwift 4.1.2時点)

Variable

VariableはRxSwift/Deprecated.swiftに定義されています。

VariableはBehaviorSubjectのwrapperで、コメントに書いてあるようにBehaviorSubjectとの違いはerrorでterminateしないことと、deallocateされたときにcompleteする点にあります。

/// Unlike `BehaviorSubject` it can't terminate with error, and when variable is deallocated
/// it will complete its observable sequence (`asObservable`)

ソースを見てみます。

Variableは自身でvar _value: Eというプロパティで値を保持していて、それをSpinLock()を使って排他制御しています。(え、spinlockなの?と思ってソースをみたらRecursiveLockで安心しましたtypealias SpinLock = RecursiveLock)

public final class Variable<Element> {

    public typealias E = Element

    private let _subject: BehaviorSubject<Element>

    private var _lock = SpinLock()

    // state
    private var _value: E

    #if DEBUG
    fileprivate let _synchronizationTracker = SynchronizationTracker()
    #endif

    /// Gets or sets current value of variable.
    ///
    /// Whenever a new value is set, all the observers are notified of the change.
    ///
    /// Even if the newly set value is same as the old value, observers are still notified for change.
    public var value: E {
        get {
            _lock.lock(); defer { _lock.unlock() }
            return _value
        }
        set(newValue) {
            #if DEBUG
                _synchronizationTracker.register(synchronizationErrorMessage: .variable)
                defer { _synchronizationTracker.unregister() }
            #endif
            _lock.lock()
            _value = newValue
            _lock.unlock()

            _subject.on(.next(newValue))
        }
    }

    /// Initializes variable with initial value.
    ///
    /// - parameter value: Initial variable value.
    public init(_ value: Element) {
        #if DEBUG
            DeprecationWarner.warnIfNeeded(.variable)
        #endif

        _value = value
        _subject = BehaviorSubject(value: value)
    }

    /// - returns: Canonical interface for push style sequence
    public func asObservable() -> Observable<E> {
        return _subject
    }

    deinit {
        _subject.on(.completed)
    }
}

BehaviorRelay

VariableはRxCocoa/Traits/BehaviorRelay.swiftに定義されています。

BehaviorRelayはBehaviorSubjectのwrapperで、コメントにも書いてあるようにBehaviorSubjectとの違いはerrorやcompletedでterminateしないことです。

Variableとの違いは、

  • RxSwiftからRxCocoaに変わった
  • BehaviorRelayではcompletedがこない

ということになります。

/// Unlike `BehaviorSubject` it can't terminate with error or completed.

ソースを見てみます。

Variableのようにvalueを保持せず、acceptで値を変更するときはBehaviorSubjectのonNext()、値を取得するときにはcomputed propertyのvalueでBehaviorSubjectのvalue()を返します。

一点気をつける点は、Variableでは値の取得がvar _value: EだったのがBehaviorRelayだとcomputed propertyになっているので、型が値型の場合、前者はmutable、後者はcopyが返るのでimmutableになります。なので特にArrayとかの場合はmutable前提の使い方をしてると影響があります。

また、BehaviorRelayでは排他はかけてませんが、BehaviorSubject側でRecursiveLockを使って排他しているので大丈夫です。

public final class BehaviorRelay<Element>: ObservableType {
    public typealias E = Element

    private let _subject: BehaviorSubject<Element>

    /// Accepts `event` and emits it to subscribers
    public func accept(_ event: Element) {
        _subject.onNext(event)
    }

    /// Current value of behavior subject
    public var value: Element {
        // this try! is ok because subject can't error out or be disposed
        return try! _subject.value()
    }

    /// Initializes behavior relay with initial value.
    public init(value: Element) {
        _subject = BehaviorSubject(value: value)
    }

    /// Subscribes observer
    public func subscribe<O: ObserverType>(_ observer: O) -> Disposable where O.E == E {
        return _subject.subscribe(observer)
    }

    /// - returns: Canonical interface for push style sequence
    public func asObservable() -> Observable<Element> {
        return _subject.asObservable()
    }
}

まとめ

BehaviorSubject, Variable, BehaviorRelayの違い

terminate

  • BehaviorSubject
    • error, completedでterminateする
  • Variable
    • errorでterminateしない。(dealloc時にcompletedでterminateする)
  • BehaviorRelay
    • terminateしない

import

  • BehaviorSubject
    • RxSwift
  • Variable
    • RxSwift
  • BehaviorRelay
    • RxCocoa

valueの取得

  • BehaviorSubject
    • function
  • Variable
    • property
  • BehaviorRelay
    • computed property