今回のエンジニアブログを担当する村田です。
今回はモバイル向けデータベースとして今後熱くなりそうな
「Realm」を触ってみたいと思います。
Realmとは?
SQLiteやCoreDataと同じモバイル向けのデータベースです。
エンジン部分はC++で実装されております。
特徴としては
- iOS(Objective-CとSwiftの両方)とAndroidをサポート
- SQLiteよりも高速
- Apache 2.0 Licenseの元、オープンソースとして公開
Realmは現在β版としてリリースされており、バージョン1.0に向けて、機能追加・バグの修正などが行われております。β版ということでプロダクトに使う際は、APIが頻繁に変わるものだと心して使う必要がありそうです。
では、iOS版である「Realm Cocoa」に触れてみます。
今回の検証環境は以下のとおりです。
- Xcode 6.3
- Realm 0.91.3
Realm 0.91.3時点では、SwiftのサポートがSwift 1.1のみなので、無難にObjective-Cで実装します。
インストール
Realmのインストール方法は、4つあります。
- マニュアル インストール
- CocoaPods
- iOS 8 Dynamic Framework
- Carthage
今回は、CocoaPodsでインストールします。
Podfile に記載する内容は、pod "Realm"
のみです。$ pod install
を実行したのち、.xcworkspace ファイルを開きます。
モデルの作成
RealmはSQLを直接叩くのではなく、O/Rマッパーのようにオブジェクトを操作します。
モデルは、RLMObjectクラスを継承して作成します。
では、Fruitモデルを作成します。
Realmを組み込んだプロジェクトをビルドすると、ファイル新規作成に「Realm」が現れるようになります。
こちらを使うと便利です。
Xcode Pluginを使うと更に便利のようですが、現在、一時的に使用できないようです。
作成したのがこちらになります。
#import <Realm/Realm.h> @interface Fruit : RLMObject <# Add properties here to define the model #> @end // This protocol enables typed collections. i.e.: // RLMArray<Fruit> RLM_ARRAY_TYPE(Fruit)
#import "Fruit.h" @implementation Fruit // Specify default values for properties //+ (NSDictionary *)defaultPropertyValues //{ // return @{}; //} // Specify properties to ignore (Realm won't persist these) //+ (NSArray *)ignoredProperties //{ // return @[]; //} @end
扱う値は、ヘッダーファイルの@interface内(4行目のところ)に@propertyで追加します。
プロパティ属性(nonatomic, strong,など)は無視されるので不要です。
Realmで扱える型はこちらです。
- NSString
- NSInteger, CGFloat, int, long, float, and double
- BOOL or bool
- NSDate
- NSData
- RLMObjectのサブクラス(一対多のリレーションシップに使用)
- RLMArray
(XはRLMObjectのサブクラス、多対多のリレーションシップに使用)
NSDataまで扱えるので、一通りのものが格納できますね。
Fruitモデルに、ID(fid)、名前(name)、値段(price)を追加します。
#import <Realm/Realm.h> @interface Fruit : RLMObject @property NSInteger fid; @property NSString *name; @property NSInteger price; @end // This protocol enables typed collections. i.e.: // RLMArray<Fruit> RLM_ARRAY_TYPE(Fruit)
今回は、IDを主キーとします。
その場合には、Fruit.mの方に+[RLMObject primaryKey]メソッドを追加します。
#import "Fruit.h" @implementation Fruit // Specify default values for properties + (NSString *)primaryKey { return @"fid"; } //+ (NSDictionary *)defaultPropertyValues //{ // return @{}; //} // Specify properties to ignore (Realm won't persist these) //+ (NSArray *)ignoredProperties //{ // return @[]; //} @end
最初からコメントアウトされているメソッドは、以下の用途に用います。
- +[RLMObject defaultPropertyValues]メソッド
デフォルト値を設定することができます。
戻り値となるNSDictionary型は、キーにプロパティ名の文字列、バリューにはデフォルト値を指定します。 - +[RLMObject ignoredProperties]メソッド
保存しないプロパティを指定することができます。
オブジェクトの追加
Realmファイルに対して書き込みの操作を行うときは、
-[RLMRealm beginWriteTransaction]
でトランザクションを開始してから操作します。
一通りの操作が終わったら
-[RLMRealm commitWriteTransaction]
でコミットします。
SQL文のトランザクションと同じですね。
#import <Realm/Realm.h> #import "Fruit.h" : - (void)writeFruit { // モデルを生成 Fruit *fruit = [[Fruit alloc] init]; // 値を設定 fruit.fid = 1; fruit.name = @"りんご"; fruit.price = 230; // デフォルトのRealmを取得 RLMRealm *realm = [RLMRealm defaultRealm]; // トランザクションを開始し書き込む [realm beginWriteTransaction]; [realm addObject:fruit]; [realm commitWriteTransaction]; } :
モデルは普通にallocで生成し、initメソッドを呼び出し初期化します。
値は @property で定義されているので、普通に設定します。
Realmファイルを簡単に取得する方法としては、
+[RLMRealm defaultRealm]
を使う方法です。defaultRealmメソッドを使うと
というパスのRealmファイルが作成され、オブジェクトが格納されます。
自分で指定したい場合は、+[RLMRealm realmWithPath]メソッドを用います。
- (void)writeFruit { : // Realmを取得 RLMRealm *realm = [RLMRealm realmWithPath:@"Realmファイルまでの絶対パス"]; // トランザクションを開始し書き込む [realm beginWriteTransaction]; :
オブジェクトの更新
更新する場合は、
+[RLMObject createOrUpdateInRealm:withObject:]
を使います。
主キーと一致するレコードが無い場合は作成され、ある場合は更新します。
- (void)writeFruit { : // IDは1のままで、それ以外を変更 fruit.name = @"みかん"; fruit.price = 150; // トランザクションを開始し作成もしくは更新 [realm beginWriteTransaction]; [Fruit createOrUpdateInRealm:realm withObject:fruit]; [realm commitWriteTransaction]; :
オブジェクトの削除
削除する場合は、
-[RLMObject deleteObject:]
を使います。
削除したいオブジェクトを渡します。
- (void)writeFruit { : // トランザクションを開始し削除 [realm beginWriteTransaction]; [Fruit deleteObject:fruit]; [realm commitWriteTransaction]; :
オブジェクトの取得
一番簡単な取得方法は、
+[RLMObject allObjects]
です。
対象モデルのオブジェクトを全て取得します。SQL文に例えるとWHERE句がない状態です。
ただし、注意点としてデフォルトRealmファイルから取得するので +[RLMRealm defaultRealm] で操作しているときしか使えませんです。
自分でファイルパスを指定した場合は、
+[RLMObject allObjectsInRealm:]
を使います。
- (void)readFruit { // デフォルトRealmから取得 RLMResults *defresults = [Fruit allObjects]; // 指定したRealmから取得 RLMRealm *realm = [RLMRealm realmWithPath:@"Realmファイルまでの絶対パス"]; RLMResults *results = [Fruit allObjectsInRealm:realm];
これらの方法だと絞り込みができないので不便です。SQL文のWHERE句を使いたいですよね。
絞り込みたいときは
- デフォルトのRealmファイルを使う場合
+[RLMObject objectsWhere:] - Realmファイルまでのパスを指定する場合
+[RLMObject objectsInRealm:where:]
を使います。
- (void)readFruit { : // デフォルトRealmから、nameが'みかん'のものを取得 RLMResults *results = [Fruit objectsWhere:@"name = 'みかん'"]; // 自分でパスを指定した場合の方法 RLMRealm *realm = [RLMRealm realmWithPath:@"Realmファイルまでの絶対パス"]; results = [Fruit objectsInRealm:realm where:@"name = 'みかん'"];
更に欲が出てきます。
このままだとエスケープ処理が大変なのでバインド機構を使いたくなります。
RealmではNSPredicateクラスを使うことができます。
- (void)readFruit { : // デフォルトRealmから、nameが'みかん'のものを取得 NSPredicate *prepared = [NSPredicate predicateWithFormat:@"name = %@", @"みかん"]; RLMResults *results = [Fruit objectsWithPredicate:prepared]; // 自分でパスを指定した場合の方法 RLMRealm *realm = [RLMRealm realmWithPath:@"Realmファイルまでの絶対パス"]; results = [Fruit objectsInRealm:realm withPredicate:prepared];
まとめ
基本的な構文を一通り試してみました。
導入も簡単でObjective-Cとは相性が良さそうに感じました。
CocoaPodsでインストールするとありませんが、公式サイトからダウンロードするとRealmファイル専用のブラウザが付いております。
このようなアイコンのアプリです(ちゃっかりβ版のマークも)。
ブラウザが標準で付いているのは嬉しいですね!
Realmのサイトは日本語に対応しております。
ドキュメントも親切に日本語対応されている点も嬉しいですね。
暗号化にも標準で対応しているようなので、今後が楽しみです。