動機
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
import
- BehaviorSubject
- Variable
- BehaviorRelay
- BehaviorSubject
- Variable
- BehaviorRelay