エンジニア

iOSでRealmを触ってみよう!

投稿日:2015年4月24日 更新日:

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

今回はモバイル向けデータベースとして今後熱くなりそうな
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のサイトは日本語に対応しております。
ドキュメントも親切に日本語対応されている点も嬉しいですね。

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

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-,

© WonderPlanet Inc.