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