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

# RealmXとは

[<img src=https://qiita-image-store.s3.amazonaws.com/0/309112/400eec52-0ad1-72af-29a9-3d8d313fb03b.png width=400px>](https://github.com/Urotea/RealmX)
(※[Github Link Card Creator](https://qiita.com/po3rin/items/eac851304cf058c532af)で作成しました。ありがとうございます！)

[Realm](https://realm.io/docs/swift/latest/)と[RxSwift](https://github.com/ReactiveX/RxSwift)を使い、DBの状態をRxのストリームに乗せて監視するマイクロライブラリです。

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

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

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

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

## インストール方法

[CocoaPods](https://cocoapods.org/)と[Carthage](https://github.com/Carthage/Carthage)に対応しています。

### cocoapods

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

```ruby
pod 'Realm-rX'
```

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

### carthage

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

```
github "Urotea/RealmX"
```

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

## DBのレコードを監視する

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

```swift
// バックグラウンドでも動くというパフォーマンス
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:){}`はそれを支援します。

```swift
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インスタンスの生成スレッドと、ストリームのスレッドが異なっても動くようになっています。

```swift
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から値を取り出すときにはそれなりの時間がかかります。

```swift
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はスレッドを越えることができません。([参考](https://realm.io/jp/blog/obj-c-swift-2-2-thread-safe-reference-sort-properties-relationships/))
Rxのオペレータである`observeOn`をResultsを引き渡す場所に挟むとエラーが発生します。これはRealmの制約です。

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

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

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

## 最後に

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