Realm は iOS/Android 向けモバイルデータベースです。従来は端末内に情報を永続化する場合、SQLite や Core Data を利用することが多かったのではないでしょうか。いずれも十分な機能が用意されていますが、要件によってオーバースペックであったり、導入ハードルが高かったのではないかと思います。
Realm は Core Data のような直感的な O/R マッパーを持ちながらも使い方がシンプルと、多くの導入メリットを持っています。WebAPI にも親和性が高いので、レスポンス JSON のモデル化やデータキャッシュとしても利用できます。今回はサンプル iOS アプリケーションを作りながら、Realm の魅力を紹介したいと思います。
本記事は、Realm 0.91 をベースに制作しております。
Xcode プロジェクトの作成
はじめに Xcode プロジェクトを作成します。ビューコントローラはすぐ作り直すため、テンプレートはどれでもかまいません。
iOS の場合、CocodPods を使えば簡単に Realm を導入できます。Xcode プロジェクトと同じフォルダに下記のように Podfile
を作成し、ターミナルから pod install
を行ってください。インストール完了後、CocoaPods により生成された Xcode ワークスペース(.xcworkspace)を開きます。
platform :ios, '7.0' source 'https://github.com/CocoaPods/Specs.git' target 'PlayRealm' do pod 'Realm', '~> 0.91' end
モデルの定義
先述のとおり、Realm は Core Data のような O/R マッパーを提供しています。モデルクラスの定義時は必ず RLMObject
を継承します。
プロパティの定義ですが、Realm は全ての属性を無視します。利用可能な型は下記に限定されます。
- ブール値 (BOOL, bool)
- 整数 (int, NSInteger, long, long long)
- 浮動小数点数 (float, double, CGFloat)
- 文字列 (NSString)
- 日付 (NSDate, 少数点以下は切り捨て)
- バイト列 (NSData)
RLMObject
の継承クラスであれば、対一関連としてプロパティに持たせることができます。加えて、対多関連を表す RLMArray
をプロパティに定義できます。
RLM_ARRAY_TYPE(Tag) // アレイ型 RLMArrayを定義するマクロ @interface Article : RLMObject @property NSString* title; @property NSString* contents; @property Author* author; // 対一関連 @property RLMArray * tags; // 対多関連 @end
JSON とのマッピング
冒頭でも述べましたが、Realm は JSON とも親和性があります。モデルクラスを下記のルールに従い JSON 構造に合わせて定義することで、一切変換ロジックを記述することなく JSON オブジェクト(NSDictionary)をモデルにマップできます。
- 階層構造に対応する RLMObject 継承クラスを定義する。
- プロパティ名と JSON オブジェクトのキー名を一致させる。
- 型を一致させる。
例えば、上記の Article
型は下記のような JSON であればそのまま読み込むことができます。
{ "title": "新しいモバイルデータベース Realm を試す", "contents": "Realm は iOS/Android 向けモバイルデータベースです。従来は...", "author": { "name": "Gaprot Dev Team", "avatar": "http://www.gaprot.jp/avatar/xxx.png" }, "tags": [ { "slug": "ios", "name": "iOS" }, { "slug": "android", "name": "Android" } ] }
あとはモデルクラスの + (instancetype)createOrUpdateInDefaultRealmWithObject:
でインスタンス化すれば、JSON からモデルオブジェクトを生成することができます。
モデル定義の注意点
モデルクラスを定義する際、下記の点に注意しないとランタイムエラーが発生することがあります。
+ (NSString*)primaryKey
でプライマリキーを返す。- プロパティの値が null を取りうる場合、
+ (NSDictionary*)defaultPropertyValues
でデフォルト値を与える。
サンプル
サンプルケースとして、Google Places API でプレイス検索を行い、その結果を Realm でモデル化して表示する iOS アプリケーションを作成しました(ソースコードをダウンロード)。
このサンプルコードでは、プレイス検索のレスポンスに合わせてモデルクラスを定義しています。また検索結果を永続化しており、再起動後であっても前回の検索結果が表示されるようになっています。
Tips
バックグラウンドで書き込みを行う
多量のデータの生成や更新を行う際、これをメインスレッドで実行してしまうと UI のレスポンスに遅延が生じてしまいます。このような場合は GCD を用いてバックグラウンドで処理を行うようにしましょう。
Realm のコンテキスト RLMRealm
は、そのインスタンスをスレッド間で共有することはできませんので、コンテキストを都度生成するようにしてください。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ RLMRealm* realm = [RLMRealm defaultRealm]; [realm beginWriteTransaction]; // データの生成や更新... [realm commitWriteTransaction]; });
永続化を行わないようにする
デフォルトの Realm コンテキスト [RLMRealm defaultRealm]
はモデルオブジェクトの永続化を行いますが、敢えて行わせない方法もあります。
RLMRealm* realm = [RLMRealm inMemoryRealmWithIdentifier:@"..."];
アプリ終了時に揮発してもよいデータは、この方法で生成したコンテキストを利用するとよいでしょう。コンテキストが解放されると、内部のデータも解放されますので注意してください。
関連オブジェクトを再帰的な削除
あるオブジェクトが削除されたとき、関連する配下のオブジェクトも削除したいことがあると思います。このような機能は残念ながら、現時点では実装されていないようです。今後のバージョンアップに期待しましょう。
参考: http://stackoverflow.com/questions/26647551/
おわりに
今回は iOS 版を取り上げましたが、Android でも同様にモバイルデータベースを容易に構築できます。まだいくつか課題や制限があるようですが、頻繁にアップデートされていますので、次第に解決されるのではないかと思います。日本語ドキュメントも用意されており、本記事で紹介できなかった下記の機能も詳しく書かれていますので、ぜひ一読することをおすすめします。
- マイグレーション
- データの暗号化
- デバッグ方法