76
48

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RxSwift4で廃止になった Variable のリプレイス

Last updated at Posted at 2017-12-22

調べてみました😼

Deprecates Variable in favor of BehaviorRelay

4.0.0-rc.0より廃止

Deprecates Variable in favor of BehaviorRelay.

VariableBehaviorRelayに取って代わる形で、廃止になります🙋🍂

廃止の経緯

Issue #1501

  • 標準的なクロスプラットフォームのコンセプトではない
    • ReactiveXの思想にないということ・・・🤔?)
  • イベント管理において拡張性が低い
  • 命名が*Relayに対して一貫していない
  • Rx内の他のものと比べてみると、メモリの管理モデルに一貫性がない(dealloc時にCompleteされる)

とのこと。

ただしDeprecation warningsは出ない

  • Variableはかなり多用されている
  • マルチスレッドでのBehaviorRelay利用には、危険なパターンがある(後述👇)
  • どうやって廃止を進めるか明確にはまだ決まっていない

などの理由があるみたいです。

Variable & BehaviorRelay

Variable とは

Deprecated.swift#L170

  • BehaviorSubjectのラッパー
  • Errorは流れない
  • 変数が解放された時にCompleteが流れる
  • 値のアクセスはvalueプロパティのgetter/setterで行う
  • import RxSwiftで利用

RxSwift + MVVMアーキテクチャで非常に使いやすいAPIです。

BehaviorRelay とは

BehaviorRelay.swift

  • BehaviorSubjectのラッパー
  • ErrorCompleteは流れない
  • 値のアクセスはvalueプロパティとaccept(_ event: Element)で行う
  • import RxCocoaで利用

accept(_ event: Element)

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

valueプロパティ

BehaviorRelay.swift
/// 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

Playground
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は、DisposeBagMainScheduler利用のために行なっています。
実際にはMainSchedulerは指定しなくても良いですが、asDriver()利用に合わせて便宜的に書いています。

asDriver()利用

Playground
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()利用

Playground
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では、以下のような書き方で値を更新することができました。

Playground
let variable = Variable(["🍎"])
variable.value.append("🍏")

BehaviorRelayではvalueプロパティはReadOnlyのため、同じような書き方はできません。

Playground
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 の今後

Issue #1501

現状、以下のような想定をされているそうです🤔💡

  • 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.

以上です🎉🎉🎉
ありがとうございました😽

Thanks to

76
48
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
76
48

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?