Swift
RxSwift

RxSwiftの Variable を get only にする

背景

ViewModel などに、 RxSwift の Variable型のプロパティを定義して使いたい場合があると思いますが、それだと ViewModel の外からもそのプロパティのvalueを変更できてしまいます。
これに対して適切にアクセス制限をかける方法を紹介します。

既存の方法

これまで同様の課題を解決するために用いられることが多かったであろう方法として、RxPropertyがあります。
RxProperty が提供するのは Variablevalueへの参照を getter のみに制限する薄いラッパーです。
これでもちろん要件は満たせるのですが、

private let _state = Variable<T>(...)
public let state: Property<T>

このように、毎回1つ余分にプロパティを定義しなければならないので、プロパティが多くなってくると煩雑さが出てきます。

別の方法

そこで今回紹介するのは、valueの setter メソッドで、新しい値と併せて任意のファイルのみでインスタンス化できる Signature を引数にとることで、外からは値が更新できないようにする方法です。
具体的には以下のような実装になります。

public final class GetOnlyVariable<Element, Signature> {
    private let variable: Variable<Element>

    public var value: Element {
        get {
            return variable.value
        }
    }

    public init(_ value: Element) {
        variable = Variable(value)
    }

    public func onNext(_ value: Element, signature: Signature) {
        variable.value = value
    }

    public var changed: Observable<Element> {
        return variable.asObservable().skip(1)
    }
}

これを

final class ViewModel {
    struct Signature {
        // fileprivate にすることで、このファイル以外からはインスタンス化できず、onNext を呼び出すことができなくなる
        fileprivate init() {}
    }
    let property = GetOnlyVariable<String?, Signature>("This is get only property")
}

こんな感じで利用します。値の更新時に

property.onNext("new value", Signature()) // ViewModel が定義されたファイルのみで Signature() が呼び出せる

とする感じです。
Signature をファイル毎に定義する必要はありますが、プロパティの数が多い時などはこの方法も候補に入るかなと思います。