RealmとCore Dataとの使い勝手比較

今まで3年くらい[Core Data](https://developer.apple.com/library/tvos/documentation/Cocoa/Conceptual/CoreData/index.html)を使っていましたが、[JOIN US](http://joinus30.com)を引き継ぎ、バージョン3.0として作り直すタイミングで、初めて[Realm](https://realm.io)を使ってみました。

色々思うところがあったので、それについて書きます。
	
特にRealmについては経験がまだ多くないので、もし間違えなどあればご容赦&ご指摘いただければと思います。

両者を使った評価を先に記すと、こんな感じでした。

|  | Core Data | Realm |
| ---| --- | --- |
| 導入のしやすさ | x | o |
| 書きやすさ | △ | o |
| DB系ライブラリとしての機能 | o | △ |
| 困った時の対応 | o (ネット情報多い) | o (サポート良い) |

# Core Dataを使ってきた印象

まずCore Dataについての記述が多くなります。
興味無い方はスキップしてください。
Core Dataじっくり使ったことないと分からない記述があると思います。

## APIが複雑

[Swift - iOSアプリでデータベースを使う時に必要な知識をまとめてみた - Qiita](http://qiita.com/merrill/items/2a3588aa98cc64b5dea0)の前半に良い感じにまとめられていますが、全貌を把握するのがちょっと大変です。

## `NSManagedObjectContext`の扱いにコツがいる


Core Dataはスレッド間で`NSManagedObjectContext`(以下`context`と表記)を使い回すことが出来ません。
(`context`とはRealmオブジェクトのようなものです。)

つまりUIスレッドをブロックしないようにバックグラウンドスレッドで保存処理などしようとすると、それ用の`context`を生成する必要があります。
さらにそれら別のコンテキストの変更内容をマージする必要があります。
このあたりの記述がけっこう面倒かつ学習コスト必要で、それゆえCore Dataの敷居が高めだったのでは、と思っています。

### `NSManagedObjectContextDidSaveNotification`を用いて各スレッドの`context`の変更監視してマージ

iOS 4以前のけっこう面倒なやり方で、とても面倒です。

### `NSManagedObjectContext`の`performBlock:`メソッドで各スレッドの`context`の変更をマージ

iOS 5で導入されたAPIで、このおかげでかなり楽になりました。
以下の記事が詳しいです。

- [iOS - CoreDataの非同期処理 - UIスレッドを止めないために - Qiita](http://qiita.com/ytka/items/8835743c5a60d3ee5b44)
- [CoreData 非同期データ処理](http://www.slideshare.net/hedjirog/core-data-14134061)

### Core DataのAPIを組み合わせて下回り作るのが大変

iOS 5でかなり実装しやすくなったとはいえ、`context`をどう扱うかという設計は実装者に委ねられています。
ベストプラクティスは [Multi-Context CoreData | Cocoanetics](https://www.cocoanetics.com/2012/07/multi-context-coredata/) だと思っています。

ここに書いてあることを理解して実装に落とし込む必要があり、なかなか大変です。
端的に言うと、`Asynchronous Saving`の項に書いてあるように、以下を用意して上記のiOS 5のAPIを用いてハンドリングする、ということです。

- 保存用の一番親の`context`(バックグラウンドスレッド)
  - これによってUIスレッドで永続化処理でのブロッキングを防ぐ
- 表示用の`context`(UIスレッド)
- 保存用の`context`(バックグラウンドスレッド)

### [MagicalRecord](https://github.com/magicalpanda/MagicalRecord)などライブラリの利用

というわけで実装するの大変かつ車輪の再発明なので、ラップしてくれるライブラリ使うと便利です。
最近は一番実績のある[MagicalRecord](https://github.com/magicalpanda/MagicalRecord)を使っていました。
これは実際にソースを追って、上記のベストプラクティス通りになっていることを確認しました。

ただ、MagicalRecordはObjective-C時代のライブラリなので、Swiftでも動くとはいえ、型も弱いしイケてないです。(あくまでSwiftぽい書き方が出来ないだけで今でも良いライブラリではあります。)

さらに、MagicalRecord使った場合も、 [iOS - CoreDataでatomicにsaveする - Qiita](http://qiita.com/kenmaz/items/89e50f94262a4d0547c5) のケアなど必要です。

Swiftでは[gitdoapp/SugarRecord](https://github.com/gitdoapp/SugarRecord)あたりが良さそうかなと思いましたが、ベストプラクティス通りの実装になっているかの確認が面倒だったりで使っていないです。

## マイグレーション実装が大変

これも正確な実装をするには、[Core Dataのマイグレーションのリファアレンス](https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreDataVersioning/Articles/Introduction.html#//apple_ref/doc/uid/TP40004399-CH1-SW1)に目を通さなければ行けなくて大変でした。

結論からいうと、大半のスキーマ変更は、良い感じにスキーマ弄って軽量マイグレーション実行すれば速くて楽に出来るのですが、その知識を得るのに時間かかったり、軽量マイグレーションで対処出来ない場合などの定義が面倒だったりします。

## パフォーマンスはどうか？

遅いといわれていますが、各種ベンチマーク見ても、そこまでいうほど遅くないと思いますし、DBアクセスのパフォーマンスがネックになるようなアプリは少ないので、あまり問題視していませんでした。

苦労ポイント中心に書きましたが、これら乗り越えれば、良い感じのライブラリなので、気に入って使っていました。

# Realmについて

ようやくRealmについての話題です(　´･‿･｀)

## 学習コストがとても低い

[Swift Docs - Realm is a mobile database: a replacement for SQLite & Core Data](https://realm.io/jp/docs/swift/latest/)を、ちょこっと実際にコード書いて試しながら1時間くらい読んだら、大体理解出来ました。

感覚的には、MagicalRecord経由でCore Dataを扱う感じでした。
さらにSwiftぽく型が強めに書ける(`RealmSwift`使った場合)ので、`SugarRecord`の方がさらに感覚が近そうです。

というわけで、実際に利用してみて感じたのは、上であげたCore Dataで苦労してきた点が、ほぼ解消された形で使いやすいAPIセットとして提供されているということでした。
逆に、すでにCore Data使いこなしたり、`MagicalRecord`にラッパーメソッドで便利にした状態と比べたら、正直そこまで大きな違いは感じ無かったです。
なので、Core Data未経験でDB扱いたい場合の選択肢としては良いと思いますが、Core Dataから乗り換えるほどかと言われると迷いますね、という印象です。

## モデル定義が簡単・差分分かりやすい

Core DataはGUIでスキーマ定義出来るのですが、それをコードとマッピングしたり、GUIが便利というよりむしろ手間が増えるし、差分も読みにくいし、みたいな状態でした。

反面、Realmは https://realm.io/jp/docs/swift/latest/#section-4 に記載の通りシンプルにクラスにプロパティ記述していけば良いので楽でした。

## サポートが良い

- [RealmのSlack](http://slack.realm.io)によるサポートが手厚い
  - @kishikawakatsumi さんからご返答いただけます
- 日本語訳が早い
  - 上記の日本語リファレンスや[ブログ](https://realm.io/jp/news/)などすぐ日本語で公開されます

個人的には英語読むのは困りませんが、日本語の方が楽に速く読めるのでありがたいです。

## マイナス点

率直にマイナスに感じた点を上げていきます。
以下は、0.96時点のもので、[現バージョンにおける制限事項](https://realm.io/jp/docs/swift/latest/#section-42)に記載の通り、今後改善するものが多いと思います。
> 現在のRealmはベータバージョンであり、バージョン1.0のリリースに向けて、継続的に機能追加および不具合の修正が行われています。


### まだ0系のバージョン

もうすぐ1.0に到達しそうですが、まだ`ベータバージョン`の0.96です。
ただ、すでにRealmを採用してリリースされたアプリがけっこうあるので、そこまで困ったことにはならないと思いますが、多少リスクとしてとらえていた方が良いと思っています。

この記述をしっかり読んでそれを承知で使った方が良いかと思います。

https://realm.io/jp/docs/swift/latest/#faq
>> Realmをプロダクション環境で使うことはできますか？

> Realmは、2012年から商用のプロダクトで利用されています。
現在のRealmを利用する場合は、RealmのObjective-C、およびSwiftのAPIが、コミュニティのフィードバックを受けて変わりうるものだとしてお使いください。 機能追加、不具合の修正も同様に考えてください。

### きめ細やかな変更通知が未サポート

Core Dataは、特に[NSFetchedResultsController](https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsController_Class/)による特定のコレクション監視が良く出来ていた一方、Realmの場合現状は「全体のどこかで変更があった」というすごく大ざっぱな単位でしか通知が来なくてかつその変更内容も分からないです。
なので、大ざっぱにUI更新するか、あるいは自前で細かい変更検知を出来る仕組みを整えるなど対応が必要になります。

### リレーション周りが弱い

https://realm.io/jp/docs/swift/latest/#section-4 の`リレーションシップ（関連）`に記載の通り、モデル定義としてはリレーションに対応しています。
しかし、Core Dataとは違い以下の状態なので、それをフレームワークがサポートしてくれることに慣れている感覚では面倒かつそれに起因するミス・バグなどに多少悩まされました。

- リレーションは自分で管理する必要がある
  - コレクションの値の追加・削除は手動で行う
  - カスケード削除未対応

### 別スレッドの`realm`オブジェクトがコミットした内容が伝播するのが遅れることがある

こちらは、個人的にかなりハマりました。
書き込み用のスレッドでその`realm`オブジェクトでコミット完了後、すぐにUIスレッドの`realm`オブジェクトでデータアクセスすると内容が更新前のものだったりしました。
クライアントコードのバグを疑ってけっこう苦労しましたが、以下の制約のようです。
`refresh()`を呼ぶようにして対応しましたが、コミットのオプションとして他の`realm`に全て変更が伝わるまで待つようなオプションが欲しいなと思いました。

https://realm-public.slack.com/archives/general/p1439446195000912 (RealmのSlackに登録していないと見られないです)
> 原理的には別スレッドの変更はトランザクションがコミットされた時点で、他の同じRealmに変更が通知されます。
ただ、それが遅れる場合がある、というか
今回だと、メインスレッドは制御を戻さないので、いったんこのメソッドを抜けるか、ランループに制御を戻さないと、
変更を受けられないと思います。
なので、タイミングの問題のときはあえて `refresh()` を呼んで、古いデータが返ってくるのを防ぐことができます。
毎回 `refresh()` を呼ぶようなラッパーやエクステンションを作ってるひともいたと思いますね。

### マイグレーション時にデフォルト値が無視される

昨日これに起因するエンバグを見つけて気づいたばかりなのですが、
`dynamic var isFoo: Bool = true` のようなプロパティを増やした時、新規インストールでは問題無いのですが、アップデートの場合 `false` になってしまいました。

よくドキュメント読んだりIssue検索したら、以下のように現状の制限とのことでした。

https://realm.io/jp/docs/objc/latest/#section-35 
> 注意することとして、初期値の設定はマイグレーションの最中には適用されません。 この問題は不具合としてこちらのIssue #1793で管理されています。

[Use default property values when adding a new column in a migration · Issue #1793 · realm/realm-cocoa](https://github.com/realm/realm-cocoa/issues/1793)が対応されるのを待ちましょう。

デフォルト値に関する記述を`dynamic var isFoo: Bool = true` と二重に書かなくてはいけなくて微妙ですが、とりあえず以下のようにマイグレーションコードを書けば対応出来ます。
ただ、Core Dataではこれは自動マイグレーションで面倒見てくれるので、それと同じ感覚で実装していると落とし穴になるかと思いますので注意です。

```swift
let config = Realm.Configuration(
    schemaVersion: nextVersion,
    migrationBlock: { migration, oldSchemaVersion in
        if (oldSchemaVersion < nextVersion) {
            migration.enumerate(User.className()) { old, new in
                guard let new = new else {
                    assert(false)
                    return
                }
                new["isFoo"] = true // newオブジェクトをUser型にキャスト出来なかったのでプロパティは文字列で扱う
            }
        }
})
Realm.Configuration.defaultConfiguration = config
```
---

というわけで、以下のような良い評判の多い`Realm`ですが、実利用するとけっこうハマったり、マイナス点もあるなと思いました。

- 使いやすい
- パフォーマンスが良い

もちろん、`Realm`についてネガティブな意見を述べたいわけではなく、こういうこと踏まえつつ適切に利用していけば良いなと思います。
僕は、アプリの要件次第ではありますが、継続して`Realm`使っていこうかなと思っています。
特に、[現バージョンにおける制限事項](https://realm.io/jp/docs/swift/latest/#section-42)が解消されていくことを願っております :bow: