読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

iOSでRealmを触ってみよう!

iOS Objective-C

今回のエンジニアブログを担当する村田です。

今回はモバイル向けデータベースとして今後熱くなりそうな
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」が現れるようになります。
スクリーンショット_2015-04-22_22_37_37
こちらを使うと便利です。
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メソッドを使うと

/Documents/default.realm

というパスの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ファイル専用のブラウザが付いております。
スクリーンショット 2015-04-24 2.18.09
このようなアイコンのアプリです(ちゃっかりβ版のマークも)。
スクリーンショット 2015-04-24 2.16.48
ブラウザが標準で付いているのは嬉しいですね!

Realmのサイトは日本語に対応しております。
ドキュメントも親切に日本語対応されている点も嬉しいですね。

暗号化にも標準で対応しているようなので、今後が楽しみです。