調べてみました😼
Deprecates Variable in favor of BehaviorRelay
4.0.0-rc.0より廃止
Deprecates Variable in favor of BehaviorRelay.
Variable
はBehaviorRelay
に取って代わる形で、廃止になります🙋🍂
廃止の経緯
- 標準的なクロスプラットフォームのコンセプトではない
- (ReactiveXの思想にないということ・・・🤔?)
- イベント管理において拡張性が低い
- 命名が
*Relay
に対して一貫していない - Rx内の他のものと比べてみると、メモリの管理モデルに一貫性がない(
dealloc
時にComplete
される)
とのこと。
ただしDeprecation warningsは出ない
-
Variable
はかなり多用されている - マルチスレッドでの
BehaviorRelay
利用には、危険なパターンがある(後述👇) - どうやって廃止を進めるか明確にはまだ決まっていない
などの理由があるみたいです。
Variable & BehaviorRelay
Variable とは
-
BehaviorSubject
のラッパー -
Error
は流れない - 変数が解放された時に
Complete
が流れる - 値のアクセスは
value
プロパティのgetter/setter
で行う -
import RxSwift
で利用
RxSwift + MVVMアーキテクチャで非常に使いやすいAPIです。
BehaviorRelay とは
-
BehaviorSubject
のラッパー -
Error
とComplete
は流れない - 値のアクセスは
value
プロパティとaccept(_ event: Element)
で行う -
import RxCocoa
で利用
accept(_ event: Element)
// Accepts `event` and emits it to subscribers
public func accept(_ event: Element) {
_subject.onNext(event)
}
value
プロパティ
/// 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()
}
Sample Codes
Variable
import RxSwift
let bag = DisposeBag()
let relay = Variable("🍎")
func bind() {
relay.asObservable()
.subscribe(onNext: { value in
print(value)
}).disposed(by: bag)
}
// Apples
bind()
relay.value = "🍏"
Output
🍎
🍏
BehaviorRelay
import RxSwift
は、DisposeBag
とMainScheduler
利用のために行なっています。
実際にはMainScheduler
は指定しなくても良いですが、asDriver()
利用に合わせて便宜的に書いています。
asDriver()利用
import RxSwift
import RxCocoa
let bag = DisposeBag()
let relay = BehaviorRelay(value: "🍎")
func bind() {
relay.asDriver()
.drive(onNext: { value in
print(value)
})
.disposed(by: bag)
}
// Apples
bind()
relay.accept("🍏")
asObservable()利用
import RxSwift
import RxCocoa
let bag = DisposeBag()
let relay = BehaviorRelay(value: "🍎")
func bind() {
relay.asObservable()
.observeOn(MainScheduler.instance)
.subscribe(onNext: { value in
print(value)
})
.disposed(by: bag)
}
// Apples
bind()
relay.accept("🍏")
Output
🍎
🍏
マルチスレッド + BehaviorRelay に注意
Variable
では、以下のような書き方で値を更新することができました。
let variable = Variable(["🍎"])
variable.value.append("🍏")
BehaviorRelay
ではvalue
プロパティはReadOnlyのため、同じような書き方はできません。
let relay = BehaviorRelay(value: ["🍎"])
relay.value.append("🍏") // error: cannot use mutating member on immutable value: 'value' is a get-only property
代替案としては以下のようなパターンが考えられます。
let relay = BehaviorRelay(value: ["🍎"])
var copy = relay.value
copy.append("🍏")
relay.accept(copy)
ですが、これはマルチスレッドで行う場合、アンチパターンと言えるでしょう。
なぜならrelay.value
をリードした後、すぐに別スレッドでrelay.value
に新たな値がセットされる可能性があります。relay.accept(copy)
の時に意図せずに上書きしてしまうことになります。
Thread A: var copy = relay.value // relayの値は["🍎"]
Thread B: relay.accept(["🍏", "🍏"]) // relayの値は["🍏", "🍏"]
Thread A: copy.append("🍏")
relay.accept(copy) // relayの値は["🍎", "🍏"]
こちらは、Issue #1501にも記載がありますが、この問題を解決する方法は今の所ないみたいです。
Variable の今後
現状、以下のような想定をされているそうです🤔💡
-
typealias Variable = BehaviorRelay
とし、解放時Complete
されるのを廃止する - Deprecation warningsを追加する
- RxSwiftから
Variable
をなくす - 次のメジャーアップデートでは、少なくとも
Variable
はRxCocoaに移す
まとめ
RxCocoaをインポートする違和感
Issue #1501でも指摘されていますが・・・
BehaviorRelay
が入っているRxCocoa
には、他にもUI関連のコンポーネントが含まれています。単純なVariable
の代用としてCleanArchitectureやMVVMなどのUIレイヤー以外でimport RxCocoa
を行うのは、違和感があると思いました。余計なインポートを行なっているように感じるためです。
とはいえ、現状、この構成がすぐに覆ることはないと思います。
わたしたちにできること
私たちにできることは、
- 早めにリプレイスについて考える(問題を早めに検知する)
- タイミングを見極める
- マルチスレッドでの操作などチームでルールを決める
などでしょうか。実際に良いルールなどあればぜひ共有いただきたいです🙋
Variable
でできたことがBehaviorRelay
ではできなくなっている!などの場合は積極的にIssue出しをしましょう。
例えば、RxSwift4のリリース当初、Can't bind ObservableType to BehaviorRelayというIssueが上がっていましたが、こちらはすぐに追加対応されました。
ちなみにaddDisposableTo
も廃止になったので注意です。
Adds Disposable extension disposed(by:) equivalent to addDisposableTo that is meant to replace it in future 4.0 version.
以上です🎉🎉🎉
ありがとうございました😽