RealmとRxSwiftを組み合わせた拡張ライブラリRealmX


RealmXとは


(※Github Link Card Creatorで作成しました。ありがとうございます!)

RealmRxSwiftを使い、DBの状態をRxのストリームに乗せて監視するマイクロライブラリです。

RealmXを使うと何ができるのか

二つの機能を提供しています。

  • DBのレコードをRxのストリームに乗せて監視する
    DBの値が更新されたら画面を更新する。などの操作が簡単に書けます。

  • Realmに対して処理を行い、completableを返す
    Rxのストリームの中でRealmを簡単に操作できます。

インストール方法

CocoaPodsCarthageに対応しています。

cocoapods

podfileに以下を書いてください。

pod 'Realm-rX'

その後pod updateすることでインストール完了です。

carthage

Cartfileに以下を書いてください。

github "Urotea/RealmX"

その後carghage updateすることでインストール完了です。

DBのレコードを監視する

RxSwiftとRealmを使ってアプリを作っていると、DBのレコードの更新をObservableで流したくなります。
Realmはレコードの変更を検知する機能を持っているので、それをRxSwiftでラップしました。
本ライブラリを導入すると、以下のように書くことができます。

// バックグラウンドでも動くというパフォーマンス
DispatchQueue.global(qos: .background).async {
    autoreleasepool {
        let realm = try! Realm()
        realm.objects(Dog.self)
        .toObservable() // 本ライブラリで提供するAPI
        .subscribe {
            print($0)
        }
    }
}

上記のコードのtoObservable()がこのライブラリが提供するメソッドです。
Realm内のDogの状態が変更された時(新しいデータの追加、削除、変更)ストリームが発火します。

ストリームに乗せてRealmを操作する

Realmの操作を非同期で実行して、完了したら通知を受け取りたい時があります。
doInTransaction(objtrct:){}はそれを支援します。

let realm = try! Realm()
let dog = Dog()
dog.age = 1

realm.doInTransaction(object: [dog]) { (realm: Realm, dogList: [Dog]) in 
     realm.add(dogList.first!)
}.subscribe(onCompleted: {
    print("insert success.")
})

ここではaddしていますが、deleteなど、他の操作を行うこともできます。

また以下のように、Realmインスタンスの生成スレッドと、ストリームのスレッドが異なっても動くようになっています。

let realm = try! Realm()
let dog = Dog()
dog.age = 1

realm.doInTransaction(object: [dog]) { (realm: Realm, dogList: [Dog]) in 
     realm.add(dogList.first!) // ここの実行スレッドはsubscribeOnで指定したスレッド
}.subscribeOn(/* どこかのスレッド */)
.subscribe(onCompleted: {
    print("insert success.")
})

使い方のススメ(toObservable)

Realmでは、可能な限りResultsで取り出す範囲を絞り込むことが無駄なI/Oを減らすコツです。
RealmのResultsに対する操作は高速に動作します。しかし、Resutlsから値を取り出すときにはそれなりの時間がかかります。

let realm = try! Realm()
realm.objects(Dog.self).filter("age > 2").toObservable().subscribe{}

このように記述することで、2才以上のDogのみ監視対象とすることができます。
また、toObservable()の返り値もResultsなので、そこでさらなる絞り込みを行うことも可能です。

とにかく、できる限りResultsで数を絞り込むことがRealmを高速に動作させるコツです。
本ライブラリはResultsをストリームで管理することで、支援します。

スレッドに関する注意

本ライブラリを使用するときにスレッドに関して2点注意する必要があります。

  • toObservable()は必ずメインスレッドでストリームを流してくる

RealmはDBのレコードを監視するときにrunloopを必要とします。基本的にこれはメインスレッドのみ持つので、監視はメインスレッドで行なっています。それにより、toObservable()もメインスレッドでストリームを流します。

  • RealmのResultsはスレッドを超えられない

RealmのResultsはスレッドを越えることができません。(参考)
RxのオペレータであるobserveOnをResultsを引き渡す場所に挟むとエラーが発生します。これはRealmの制約です。

let realm = try! Realm()
realm.objects(Dog.self).toObservable() // メインスレッドでストリームを流す
    .observeOn(/* どこかのスレッド */)  // これはエラーを吐く
    .subscribe{}

ですので、もしバックグラウンドスレッドに持って行く場合は、Resultsから値を取り出すことでスレッドを越えることができます。

let realm = try! Realm()
realm.objects(Dog.self).toObservable() // メインスレッドでストリームを流す
    .map{ results -> DogStruct in /* ResultsからStructにmapする */}
    .observeOn(/* どこかのスレッド */)
    /* 何かしらの処理 */
    .subscribe{}

最後に

モバイルアプリはUIスレッドをブロックしないように気を付けないと簡単に画面が固まってしまいます。
あらゆるイベントをストリームに流してレッツリアクティブ。
(あと祝!初OSS)