古いバージョンのドキュメントを表示しています。
最新版のドキュメントはこちらからご覧になれます

はじめに

インストール

手動でのインストール (Objective‑C & Swift)

  1. ここから 最新のRealm をダウンロードしてください。
  2. この中にある ios/ または osx/ フォルダから Realm.framework をXcodeのナビゲータエリアにドラッグ&ドロップしてください。この時、 Copy items if needed にチェックが入ってることを確認してから Finish をクリックしてください。
  3. ナビゲータエリアでプロジェクトをクリックし、アプリのTargetを選択します。 Build Phases タブの Link Binary with Libraries から、 libc++.dylib を追加してください。
  4. Swiftの場合は、 さらに Swift/RLMSupport.swift も同様にドラッグ&ドロップをし、 Copy items if needed にチェックが入った状態で、ナビゲータエリアに追加してください。
  5. OSXプロジェクトの場合は、Build Phase タブのパネルの左上にある + ボタンをクリックし、New Copy Files Phase を選択して新しくビルドフェーズを追加してください。 そして名前を Copy Frameworks に変更し、 DestinationFrameworks に変え、 Realm.framework を追加してください。

CocoaPodsを使ってのインストール (Objective‑Cのみ)

CocoaPods を使っている場合は…

  1. Podfileに pod "Realm" を追加してください。
  2. コマンドラインで pod install を実行してください。
  3. CocoaPodsによって作成された .xcworkspace をXcodeで開いてください。

Xcode Plugin

XcodeのPluginを使うと、Realmで使うModelクラス(以下、Realmモデルクラス)を簡単に作成できます。

RealmのXcodePluginの最も簡単なインストール方法は、 Alcatrazを使って、“RealmPlugin” を追加することです。もちろん、手動でもインストールでき、最新のRealm の中にある plugin/RealmPlugin.xcodeproj を開くことで、プラグインを追加できます。 追加後は、Xcodeを再起動する必要があります。ファイルを新規追加する画面(File > New > File… か ⌘N) で、 Realm Model Object が選択できるようになります。

Realm Browser

RealmBrowser は、Realmの中で使われるている .realm ファイルを閲覧、編集するMacアプリです。最新のRealmbrowser/ フォルダの中に入っています。 また、Tools > Generate demo database を選択することでサンプルデータを含んだ、テスト用のRealmデータベースを作ることもできます。

サンプル

最新のRealmexamples/ファルダにObjective‑C, Swift, RubyMotionそれぞれのiOS/Macのサンプルコードがあります。RealmのMigration, UITableViewControllerとの使い方、Encryption, コマンドラインツールなど、様々な機能が紹介されていますので是非、ご参考にしてください。

ヘルプ

  • community newsletter に参加することで定期的にRealmに関するのTipsやUseCase, ブログポストやチュートリアルなど、Realmの最新情報がGetできます。
  • StackOverflow: 以前の質問はStackOverflowで #realm をご覧ください。
  • Twitter: お気軽に @realm かハッシュタグ #realm でツイートしてください。
  • Email: [email protected]

モデル

Realmで使うモデルクラスは、一般的なObjective‑Cのインターフェイスで、@propertyを使って定義できます。 RealmObjectクラスまたは、自分で作ったRealmモデルクラスのサブクラスを作ることで、簡単に作ることができます。 また、他のObjective‑Cクラスのように、メソッドやプロトコルを追加することもできます。

注意することは、Realmモデルクラスのインスタンスは、他のスレッドに渡して使うことができません。また、Realmモデルクラスのプロパティのインスタンス変数に直接、アクセスすることはできません。

XcodePlugin をお使いの場合は、 “New File…” からテンプレートを選択することでRealmモデルクラスを簡単に作れます。

リレーションシップやネストしたデータ構造も、他のRLMObjectのサブクラスやRLMArrayを使って作れます。

#import <Realm/Realm.h>

@class Person;

// Dog model
@interface Dog : RLMObject
@property NSString *name;
@property Person   *owner;
@end
RLM_ARRAY_TYPE(Dog) // define RLMArray<Dog>

// Person model
@interface Person : RLMObject
@property NSString      *name;
@property NSDate        *birthdate;
@property RLMArray<Dog> *dogs;
@end
RLM_ARRAY_TYPE(Person) // define RLMArray<Person>

// Implementations
@implementation Dog
@end // none needed

@implementation Person
@end // none needed
import Realm

// Dog model
class Dog: RLMObject {
    dynamic var name = ""
    dynamic var owner: Person? // Can be optional
}

// Person model
class Person: RLMObject {
    dynamic var name = ""
    dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
    dynamic var dogs = RLMArray(objectClassName: Dog.className())
}

詳しくは RLMObject をご覧ください。

プロパティの型

Realmでは、以下のプロパティが使えます。 BOOL, bool, int, NSInteger, long, float, double, CGFloat, NSString, NSDate, NSData

一対一や一対多のようなリレーションシップのために、RLMArray\<_Object_\>, RLMObject のプロパティも定義できます。

プロパティ属性

Realmでは、 nonatomic, atomic, strong, copy, weak のようなObjective‑Cのプロパティ属性が無視されます。 Realmの中では、独自の最適化された属性が使われます。 そのため、混乱を避けるために、Realmモデルクラスを宣言するとき、プロパティ属性を付けないことを推奨しています。 しかし、プロパティ属性を付けた場合、RLMObject がRealmに保存されるまで有効になります。 setter, getter 属性でセッター/ゲッターの名前を変えた場合、RLMObjectがRealmに追加される前かどうかに関わらず使うことができます。

Realmモデルクラスのカスタマイズ

クラスメソッドをオーバーライドすることで、さらに機能を追加することもできます:

オブジェクトの永続化

Realmへのオブジェクトの追加、変更、削除は、必ずトランザクションを使って行ってください。

Realmモデルクラスは、インスタンス化し、他のオブジェクトと同じように使います。スレッド間やアプリの再起動時にRealmオブジェクトのデータを共有するときは、Realmにデータを一度保存しなければいけません。 これらの操作は、トランザクションの中で行ってください。以下のようにオブジェクトを追加します。

// Create object
Person *author = [[Person alloc] init];
author.name    = @"David Foster Wallace";

// Get the default Realm
RLMRealm *realm = [RLMRealm defaultRealm];
// You only need to do this once (per thread)

// Add to Realm with transaction
[realm beginWriteTransaction];
[realm addObject:author];
[realm commitWriteTransaction];
// Create a Person object
let author = Person()
author.name = "David Foster Wallace"

// Get the default Realm
let realm = RLMRealm.defaultRealm()
// You only need to do this once (per thread)

// Add to the Realm inside a transaction
realm.beginWriteTransaction()
realm.addObject(author)
realm.commitWriteTransaction()

オブジェクトをRealmに追加した後は、ずっと使うことができ、全ての変更が記録されていきます。(ただし、変更時は、トランザクションを使わなければいけません) もし、同じRealmを複数のスレッドで共有している場合、トランザクションがコミットされた時点で、別のスレッドにもその変更が適用されます。

書き込み処理が行われている間は、他の処理をブロックしていることになります。これは、他のPersistenceシステムでも同じことが起こり、一般的なベストプラクティスを使うことをオススメします。 バックグランド処理 をご覧ください。

RealmはMVCCアーキテクチャーであるため、Writeトランザクションが開始されている状態でも、読み込み処理は正しくできます。同時に複数のスレッドから書き込みする場合でない限り、長めのWriteトランザクションを使うことをオススメします。

詳しくは、RLMRealmRLMObject をご覧ください。

クエリ

クエリを実行するとRLMObjectオブジェクトを含んだ、RLMResultsオブジェクトが結果として返ってきます。 RLMResultは、NSArrayと似たようなインターフェイスを持ち、添え字アクセスでオブジェクトにアクセスすることもできます。 NSArrayと違う点は、RLMResultは、RLMObjectのみを含むことができるという点です。 詳しくは、RLMResults をご覧ください。

Realm上での、プロパティアクセスを含む、全てのクエリは遅延評価されてます。プロパティにアクセスした時に、初めてデータが読み込まれます。

クエリを実行したときに返ってくる結果は、データのコピーではありません。トランザクションを使ってそのデータを変更した場合、ディスクのデータを変更したことになります。 また、RLMResultに含まれるRLMObjectから、 関連 のあるオブジェクトをフェッチすることもできます。

オブジェクトの取得

最も、基本的なオブジェクトのフェッチ方法は、[RLMObject allObjects] です。 これは、指定したクラスのインスタンスをdefaultRelam内から全てフェッチしてきます。

// Query the default Realm
RLMResults *dogs = [Dog allObjects]; // retrieves all Dogs from the default Realm

// Query a specific Realm
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // get a specific Realm
RLMResults *otherDogs = [Dog allObjectsInRealm:petsRealm]; // retrieve all Dogs from that Realm
// Query the default Realm
let dogs = Dog.allObjects()

// Query a specific Realm
let petsRealm = RLMRealm(path: "pets.realm")
let otherDogs = Dog.allObjectsInRealm(petsRealm)

Predicateを使ったフェッチ

普段からNSPredicateの扱いに慣れているなら、Realmでのオブジェクトの取得はすでに知っているも当然です。 RLMObject, RLMRealm, RLMArray, RLMResultは全て、NSPredicateインスタンスまたは、Predicateの構文を使ってのオブジェクトのフェッチをサポートしています。

例えば、以下のように [RLMObject objectsWhere:] は、Dogクラスの color = tan で、nameの値が B からはじまるDogクラスのインスタンスをdefaultRealm内から全てフェッチしてきます。

// Query using a predicate string
RLMResults *tanDogs = [Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"];

// Query using an NSPredicate object
NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@",
                                                     @"tan", @"B"];
tanDogs = [Dog objectsWithPredicate:pred];
// Query using a predicate string
var tanDogs = Dog.objectsWhere("color = 'tan' AND name BEGINSWITH 'B'")

// Query using an NSPredicate object
let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
tanDogs = Dog.objectsWithPredicate(predicate)

詳しくは、Apple’s Predicates Programming Guide をご覧ください。Realmでは、たくさんのPredicate構文をサポートしています。

  • 比較が、プロパティ名と定数で使えます。少なくとも一つのオペランドは、プロパティ名でないといけません。
  • 比較演算子 ==, <=, <, >=, >, !=, BETWEEN が、int, long, float, double, NSDate で使えます。 Ex.) age == 45
  • オブジェクトの同一性 ==, != Ex.) [Employee objectsWhere:@"company == %@", company]
  • bool型では、==, != が使えます。
  • NSString型、NSData型では、==, !=, BEGINSWITH, CONTAINS, ENDSWITH が使えます。 Ex.) name CONTAINS 'Ja'
  • 文字列の比較 Ex.) name CONTAINS[c] 'Ja'
  • 論理演算: “AND”, “OR”, “NOT” が使えます。 Ex.) name BEGINSWITH 'J' AND age >= 32
  • いずれかの条件と一致するかどうかの IN Ex.) name IN {'Lisa', 'Spike', 'Hachi'}
  • nil との比較 Ex.) [Company objectsWhere:@"ceo == nil"]
  • いずれかの要素が条件と一致するかどうかの ANY Ex.) ANY student.age < 21
  • 範囲が指定できる BETWEEN Ex. RLMResults *results = [Person objectsWhere:@"age BETWEEN %@", @[42, 43]];

詳しくは、[RLMObject objectsWhere:] をご覧ください。

ソート

RLMResultは、プロパティの値でソートすることができます。 たとえば、以下の例では、[RLMResults sortedResultsUsingProperty:ascending:] によって、特定のDogオブジェクトをnameの値をアルファベット順でソートしてから取り出しています。

// Sort tan dogs with names starting with "B" by name
RLMResults *sortedDogs = [[Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"]
                               sortedResultsUsingProperty:@"name" ascending:YES];
// Sort tan dogs with names starting with "B" by name
var sortedDogs = Dog.objectsWhere("color = 'tan' AND name BEGINSWITH 'B'").sortedResultsUsingProperty("name", ascending: true)

詳しくは、[RLMObject objectsWhere:] [RLMResults sortedResultsUsingProperty:ascending:] をご覧ください。

連続したクエリの実行

他のデータベースと比較した時に、Realmを使う利点として、小さなオーバーヘッドで、連続してクエリが実行できる点が挙げられます。 たとえば、color = tan で、 nameB からはじまる Dogオブジェクトをフェッチしたい場合、以下のように連鎖的にメソッドを呼び出すことができます。

RLMResults *tanDogs = [Dog objectsWhere:@"color = 'tan'"];
RLMResults *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH 'B'"];
let tanDogs = Dog.objectsWhere("color = 'tan'")
let tanDogsWithBNames = tanDogs.objectsWhere("name BEGINSWITH 'B'")

Realm

デフォルトRealm

すでにお気付きだと思いますが、これまで [RLMRealm defaultRealm] を呼ぶことで、変数 realm を初期化してきました。 このメソッドは、アプリのDocumentsフォルダにある、“default.realm” ファイルのRLMRealmオブジェクトを返します。

その他のRealm

場合によっては、複数のRealmが必要な時があります。 詳しくは、 [RLMRealm realmWithPath:] [RLMRealm realmWithPath:readOnly:error:] をご覧ください。

[RLMRealm realmWithPath:] で引数として渡すパスは、書き込み可能である必要があります。 Realmファイルの基本的な保存場所は、iOSでは、”Documents” フォルダ、OSXでは、”Application Support” フォルダです。

スレッド間での実行

複数のスレッドで、同じRealmを使う場合は、各スレッドで[RLMRealm defaultRealm], [RLMRealm realmWithPath:] または [RLMRealm realmWithPath:readOnly:error:] でインスタンスをそれぞれ生成する必要があります。 Realmファイルへのパスさえ同じであれば、RLMRealmオブジェクトはディスク上で同じものを指します。 複数のスレッド間での、RLMRealmインスタンスの共有はサポートされていません。

In-Memory Realm

基本的には、Realmは、ディスクにデータを保存しますが、[RLMRealm inMemoryRealmWithIdentifier:] を呼ぶことで、データをメモリーに保存するIn-MemoryなRealmオブジェクトを作ることができます。

RLMRealm *realm = [RLMRealm inMemoryRealmWithIdentifier:@"MyInMemoryRealm"];
let realm = RLMRealm.inMemoryRealmWithIdentifier("MyInMemoryRealm")

In-memoryなRealmでは、データをディスクに保存しません。それによりアプリの再起動時には以前に保存したデータを使用することができませんが、Query, Relationship, thread-safety は使用することができます。 これは、ディスクへの読み書きに伴うオーバーヘッドがない分、柔軟なデータアクセス機能が必要なとき有効な機能です。

注意: スコープを外れ、In-MemoryなRealmインスタンスへの参照がなくなると、そのRealm内に保存されている、全てのデータは解放されます。アプリの起動中は、In-MemoryなRealmインスタンスへの強参照を常に保持しておく必要があります。

関連

RLMObjectは、プロパティに RLMObject, RLMArray\<_Object_\>を宣言することで、他のオブジェクトと関連付けを定義することができます。 RLMArrayは、NSArrayと似たようなインターフェイスを持ち、添え字アクセスでオブジェクトにアクセスすることもできます。NSArrayと違う点は、RLMArrayは、RLMObjectのみを含むことができるという点です。 詳しくは、RLMArray をご覧ください。

以前にPersonモデルを作成したと思います。ここでは、Dogモデルを作成し関連付けを行ってみましょう。

// Dog.h
@interface Dog : RLMObject
@property NSString *name;
@end
class Dog: RLMObject {
    dynamic var name = ""
}

To-One

モデル間で多対一や一対一の関連性を持たせたい場合は、RLMObjectモデルクラスのプロパティを宣言します:

// Dog.h
@interface Dog : RLMObject
... // other property declarations
@property Person *owner;
@end
class Dog: RLMObject {
    ... // other property declarations
    dynamic var owner: Person?
}

以下のようにこのプロパティを使えます:

Person *jim = [[Person alloc] init];
Dog    *rex = [[Dog alloc] init];
rex.owner = jim;
let jim = Person()
let rex = Dog()
rex.owner = jim

RLMObjectをプロパティで使うと、ネストされたプロパティにシンプルなシンタックスでアクセスができます。 たとえば、上記の例で rex.owner.address.country とようにすると、自動的に必要なプロパティの値をフェッチされます。

To-Many

プロパティに RLMArray\<_Object_\> を宣言することで、対多の関連性を定義できます。RLMArrayは、RLMObjectを含み、NSMutableArrayと似たようなインターフェイスを持ちます。

PersonクラスにDogクラスの対多関連を持たせるとします。まず、RLMArray<Dog> を定義します。 これは、以下のように関連するモデルの@interfeceの下にマクロを書きます。

//Dog.h
@interface Dog : RLMObject
... // property declarations
@end

RLM_ARRAY_TYPE(Dog) // Defines an RLMArray<Dog> type
// Not needed in Swift

RLMArray<Dog> 型を宣言できるようになりました。

// Person.h
@interface Person : RLMObject
... // other property declarations
@property RLMArray<Dog> *dogs;
@end
class Person: RLMObject {
    ... // other property declarations
    dynamic var dogs = RLMArray(objectClassName: Dog.className())
}

これで、RLMArrayプロパティに対してアクセス、オブジェクトを追加したりできるようになりました。

// Jim is owner of Rex and all dogs named "Fido"
RLMArray *someDogs = [Dog objectsWhere:@"name contains 'Fido'"];
[jim.dogs addObjects:someDogs];
[jim.dogs addObject:rex];
let someDogs = Dog.objectsWhere("name contains 'Fido'")
jim.dogs.addObjects(someDogs)
jim.dogs.addObject(rex)

通知

Realm内のデータの更新がかかる毎に、他のRealmインスタンスに通知を送り、それを受け取ることができます。 通知を受け取る方法は、以下のように事前にブロックとして登録しておく必要があります。

// Observe Realm Notifications
self.token = [realm addNotificationBlock:^(NSString *note, RLMRealm * realm) {
    [myViewController updateUI];
}];
// Observe Realm Notifications
let token = realm.addNotificationBlock { note, realm in
    viewController.updateUI()
}

Notificaiton Token が保持されてる限り、登録されたNotificationは存在し続けます。 更新などをするためにクラス上で Notification Token の強参照を保持しておく必要があります。 Notification Token が、解放された時点で登録されたNotificationは自動的に抹消されます。

詳しくは、[Realm addNotificationBlock:] [Realm removeNotificationBlock:] をご覧ください。

バックグラウンド処理

Realmは、バッチ処理として膨大なデータを追加するとき、すごく効率よく動作します。 メインスレッドのブロッキングを避けるためGrand Central Dispatchを使い、その中にトランザクションを書きます。 RLMRealmオブジェクトは、スレッドセーフではないため、複数のスレッド間では共有することことができません。それぞれ、これから読み書きしたいthread/dispatch_queue内でRLMRealmオブジェクトを生成する必要があります。 以下は、バックグランド処理で100万個のオブジェクトを追加する例です。

dispatch_async(queue, ^{    
    
  // Get realm and table instances for this thread
  RLMRealm *realm = [RLMRealm defaultRealm];
  
  // Break up the writing blocks into smaller portions
  // by starting a new transaction
  for (NSInteger idx1 = 0; idx1 < 1000; idx1++) {
    [realm beginWriteTransaction];
    
    // Add row via dictionary. Property order is ignored.
    for (NSInteger idx2 = 0; idx2 < 1000; idx2++) {
      [Person createInRealm:realm
                 withObject:@{@"name"      : [self randomString],
                              @"birthdate" : [self randomDate]}];
    }

    // Commit the write transaction
    // to make this data available to other threads
    [realm commitWriteTransaction];
  }
});
dispatch_async(queue) {
  // Get realm and table instances for this thread
  let realm = RLMRealm.defaultRealm()

  // Break up the writing blocks into smaller portions
  // by starting a new transaction
  for idx1 in 0..<1000 {
    realm.beginWriteTransaction()

    // Add row via dictionary. Property order is ignored.
    for idx2 in 0..<1000 {
      Person.createInDefaultRealmWithObject([
        "name": "\(idx1)",
        "birthdate": NSDate(timeIntervalSince1970: idx2)
      ])
    }

    // Commit the write transaction
    // to make this data available to other threads
    realm.commitWriteTransaction()
  }
}

詳しくは、RLMRealm をご覧ください。

REST API

Realmでは、簡単に REST API と統合ができ、いくつかの利点があります。

  • REST API のみだと常にネットとの接続が必要ですが、データをキャッシュすることでオフラインでも、アプリを動作させることができます。
  • 完全にデータをキャッシュさせておくことで、REST API だけでは、不可能なローカルでクエリを実行したり、データを閲覧したりできます。
  • データを保存させておくことで、サーバーサイド側での処理を新しい変更だけをロードするものだけに減らすことができます。

ベストプラクティス

  1. 非同期リクエスト — リクエストを投げる時や、他のブロッキング操作は、UIスレッドを止めてしまう恐れがあるので、バックグラウンド処理で行うことが推奨されています。これと同じ理由で、Realmでの膨大なデータの変更は、バックグラウンド処理で行うべきです。バックグラウンドで行われた変更に対応するには、Notifications 機能を使いましょう。
  2. 膨大なデータのキャッシュ - 可能な時に事前にデータをRealmにフェッチし保存させておくことをオススメします。そのようにしておくと、いつでもローカルで保存してあるデータに対してクエリが投げられます。
  3. 挿入と更新 - Primaryキーのような単一な識別子をデータセットが持っている場合、[RLMObject createOrUpdateInDefaultRealmWithObject:] を使うことで、REST APIからレスポンスを受け取ったときに、より簡単にデータを挿入/更新することができます。このメソッドは、更新が必要かどうかや、既に存在するデータかどうかなどを自動的にチェックしてくれます。

Example

以下の例は、シンプルなRealmとREST APIとの連携例です。この例は、Foursquare APIからJSONデータをフェッチしてき、default RealmにRealm Objectとして保存している例です。 これに似たようなユースケース例として参考に、この ビデオ をご覧ください。 まず、Default Realmのインスタンスを生成します。そして、APIからデータをフェッチしてきます。 より簡単にするためにここでは、[NSData initWithContentsOfURL] を使っています。

// Call the API
NSData *response = [[NSData alloc] initWithContentsOfURL:
                    [NSURL URLWithString:@"https://api.foursquare.com/v2/venues/search?near=San%20Francisco&limit=50"]];

// Deserialize the response to JSON
NSDictionary *json = [[NSJSONSerialization
                       JSONObjectWithData:response
                                  options:kNilOptions
                                    error:&error] objectForKey:@"response"];
// Call the API
let url = NSURL(string: "https://api.foursquare.com/v2/venues/search?near=San%20Francisco&limit=50")
let response = NSData(contentsOfURL: url)

// De-serialize the response to JSON
let json = NSJSONSerialization.JSONObjectWithData(response,
    options: NSJSONReadingOptions(0),
      error: nil)["response"]

このようなvenues(開催地)のデータのJSON配列がレスポンスとして返ってきます。

{
  "venues": [
    {
      "id": "4c82f252d92ea09323185072",
      "name": "Golden Gate Park",
      "contact": {
        "phone": "4152522590"
      },
      "location": {
        "lat": 37.773835608329,
        "lng": -122.41962432861,
        "postalCode": "94103",
        "cc": "US",
        "state": "California",
        "country": "United States"
      }
    }
  ]
}

JSONをRealmにインポートする方法は、いくつか存在します。カスタムメソッドを作り、手動でRLMObjectのそれぞれのプロパティに配置することもできます。 この例の見どころは、直接NSDicionaryに挿入する代わりに、自動的にRLMObjectを作っているところです。 これをきちんと動作させるためには、RLMObjectのプロパティの構造と、JSONのkeyを完全に一致させておく必要があります。 もし、それらが違っている場合、そのプロパティまたはkeyは無視されます。以下のRLMObjectの定義では、問題なく動作します。

// Contact.h
@interface Contact : RLMObject
@property NSString *phone;
@end

@implementation Contact
+ (NSString)primaryKey {
    return @"phone";
}
@end
RLM_ARRAY_TYPE(Contact)

// Location.h
@interface Location : RLMObject
@property double lat; // latitude
@property double lng; // longitude
@property NSString *postalCode;
@property NSString *cc;
@property NSString *state;
@property NSString *country;
@end

@implementation Location
@end
RLM_ARRAY_TYPE(Location)

// Venue.h
@interface Venue : RLMObject
@property NSString *id;
@property NSString *name;
@property Contact  *contact;
@property Location *location;
@end

@implementation Venue
+ (NSString)primaryKey {
    return @"id";
}
@end
RLM_ARRAY_TYPE(Venue)
class Contact: RLMObject {
    dynamic var phone = ""

    class func primaryKey() -> String! {
        return "phone"
    }
}

class Location: RLMObject {
    dynamic var lat = 0.0  // latitude
    dynamic var lng = 0.0  // longitude
    dynamic var postalCode = ""
    dynamic var cc = ""
    dynamic var state = ""
    dynamic var country = ""
}

class Venue: RLMObject {
    dynamic var id = ""
    dynamic var name = ""
    dynamic var contact = Contact()
    dynamic var location = Location()

    class func primaryKey() -> String! {
        return "id"
    }
}

結果のデータが配列として返ってくるので、[Venue createInDefaultRealmWithObject:]を毎回呼びオブジェクトを作らなければいけません。 この例は、JSONデータからVenueオブジェクトを複数作りdefaultRealmに追加してます。

//Extract the array of venues from the response
NSArray *venues = json[@"venues"];

RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
// Save one Venue object (and dependents) for each element of the array
for (NSDictionary *venue in venues) {
    [Venue createOrUpdateInDefaultRealmWithObject:venue];
}
[realm commitWriteTransaction];
//Extract the array of venues from the response
let venues = json["venues"] as [NSDictionary]

let realm = RLMRealm.defaultRealm()
realm.beginWriteTransaction()
// Save one Venue object (and dependents) for each element of the array
for venue in venues {
    Venue.createOrUpdateInDefaultRealmWithObject(venue)
}
realm.commitWriteTransaction()

マイグレーション

データベースを使ってる場合、時間が経つにつれ、データモデルというのは変更されていくものです。 Realmでのデータモデルは、シンプルなObjective‑Cインターフェイスで定義されてますので、インターフェイスに変更を加えるだけで、簡単にデータモデルを変えられます。 たとえば、以下の Person.h について考えてみてください。

@interface Person : RLMObject
@property NSString *firstName;
@property NSString *lastName;
@property int age;
@end
class Person: RLMObject {
    dynamic var firstName = ""
    dynamic var lastName = ""
    dynamic var age = 0
}

ここで、firstNamelastName を一つにして、fullName プロパティが必要になったとします。 そこで以下のような単純な変更をインターフェイスに加えることにします。

@interface Person : RLMObject
@property NSString *fullName;
@property int age;
@end
class Person: RLMObject {
    dynamic var fullName = ""
    dynamic var age = 0
}

ここでのポイントは、もし以前に前のデータモデルでのデータが保存されている場合、新しく定義し直したデータモデルとディスクに保存されている古いデータモデルとで不都合が生じてしまいます。 このままマイグレーションを実行しないままでは、Exception が生じます。

マイグレーションの実行

マイグレーション処理は、[RLMRealm setSchemaVersion:withMigrationBlock:] を呼ぶ時に引数として渡すブロックの中に定義します。 このとき、スキーマに関連しているバージョン毎にマイグレーション処理の分岐を書く必要があります。 マイグレーションブロックの中には、全ての古いデータモデルから新しいデータモデルへ移行させるためのロジックが書かれていないといけません。 [RLMRealm setSchemaVersion:withMigrationBlock:] が呼ばれた後に、自動的にマイグレーションブロックが実行されます。

たとえば、上記の Person クラスのマイグレーションについて考えてみましょう。 最低限必要なマイグレーション処理は、以下のようなものです:

// Inside your [AppDelegate didFinishLaunchingWithOptions:]

// Notice setSchemaVersion is set to 1, this is always set manually. It must be
// higher than the previous version (oldSchemaVersion) or an RLMException is thrown
[RLMRealm setSchemaVersion:1
            forRealmAtPath:[RLMRealm defaultRealmPath] 
        withMigrationBlock:^(RLMMigration *migration, NSUInteger oldSchemaVersion) {
  // We haven’t migrated anything yet, so oldSchemaVersion == 0
  if (oldSchemaVersion < 1) {
    // Nothing to do!
    // Realm will automatically detect new properties and removed properties
    // And will update the schema on disk automatically
  }
}];

// now that we have called `setSchemaVersion:withMigrationBlock:`, opening an outdated
// Realm will automatically perform the migration and opening the Realm will succeed
[RLMRealm defaultRealm];
// Inside your application(application:didFinishLaunchingWithOptions:)

// Notice setSchemaVersion is set to 1, this is always set manually. It must be
// higher than the previous version (oldSchemaVersion) or an RLMException is thrown
RLMRealm.setSchemaVersion(1, forRealmAtPath: RLMRealm.defaultRealmPath(),
                         withMigrationBlock: { migration, oldSchemaVersion in
  // We haven’t migrated anything yet, so oldSchemaVersion == 0
  if oldSchemaVersion < 1 {
    // Nothing to do!
    // Realm will automatically detect new properties and removed properties
    // And will update the schema on disk automatically
  }
})
// now that we have called `setSchemaVersion:withMigrationBlock:`, opening an outdated
// Realm will automatically perform the migration and opening the Realm will succeed
// i.e. RLMRealm.defaultRealm()

Realmによって自動的にスキーマが更新されることを示すためにここでは、空のブロックでマイグレーションを実行しています。

これは最低限のマイグレーションですが、おそらく何かデータを新しいプロパティ(ここでは、fullName)に入れるために、ここに処理を記述すると思います。 マイグレーションブロックの中では、特定の型の列挙処理を行うために [RLMMigration enumerateObjects:block:] を呼ぶことができます。 下記では、必要なマイグレーションロジックを適用しています。 変数 oldObject を使って既にあるデータにアクセスし、新しく更新するデータには変数 newObject を使ってアクセスしています。

// Inside your [AppDelegate didFinishLaunchingWithOptions:]

[RLMRealm setSchemaVersion:1 
            forRealmAtPath:[RLMRealm defaultRealmPath] 
        withMigrationBlock:^(RLMMigration *migration, NSUInteger oldSchemaVersion) {
  // We haven’t migrated anything yet, so oldSchemaVersion == 0
  if (oldSchemaVersion < 1) {
    // The enumerateObjects:block: method iterates
    // over every 'Person' object stored in the Realm file
    [migration enumerateObjects:Person.className
                          block:^(RLMObject *oldObject, RLMObject *newObject) {    
      
      // combine name fields into a single field
      newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",
                                         oldObject[@"firstName"],
                                         oldObject[@"lastName"]];          
    }];
  }
}];
// Inside your application(application:didFinishLaunchingWithOptions:)

RLMRealm.setSchemaVersion(1, forRealmAtPath: RLMRealm.defaultRealmPath(),
                         withMigrationBlock: { migration, oldSchemaVersion in
  if oldSchemaVersion < 1 {
    // The enumerateObjects:block: method iterates
    // over every 'Person' object stored in the Realm file
    migration.enumerateObjects(Person.className()) { oldObject, newObject in
      // combine name fields into a single field
      let firstName = oldObject["firstName"] as String
      let lastName = oldObject["lastName"] as String
      newObject["fullName"] = "\(firstName) \(lastName)"
    }
  }
})

一度、マイグレーション処理が適応されると、その後はいつもと同じようにRralm上の全てのオブジェクトに対してアクセスできます。

バージョンの追加方法

Person クラスが、以前に異なる2つのデータモデルをとっていた場合を考えてみましょう:

// v0
@interface Person : RLMObject
@property NSString *firstName;
@property NSString *lastName;
@property int age;
@end

// v1
@interface Person : RLMObject
@property NSString *fullName; // new property
@property int age;
@end

// v2
@interface Person : RLMObject
@property NSString *fullName;
@property NSString *email;   // new property
@property int age;
@end
// v0
class Person: RLMObject {
    dynamic var firstName = ""
    dynamic var firstName = ""
    dynamic var age = 0
}

// v1
class Person: RLMObject {
    dynamic var fullName = "" // new property
    dynamic var age = 0
}

// v2
class Person: RLMObject {
    dynamic var fullName = ""
    dynamic var email = "" // new property
    dynamic var age = 0
}

ここでのマイグレーションブロックの中に書くロジックは、以下のようになります。

[RLMRealm setSchemaVersion:2 forRealmAtPath:[RLMRealm defaultRealmPath] 
                         withMigrationBlock:^(RLMMigration *migration, 
                                              NSUInteger oldSchemaVersion) {
  // The enumerateObjects:block: method iterates
  // over every 'Person' object stored in the Realm file
  [migration enumerateObjects:Person.className 
                        block:^(RLMObject *oldObject, RLMObject *newObject) {
    // Add the 'fullName' property only to Realms with a schema version of 0
    if (oldSchemaVersion < 1) {
      newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",
                                oldObject[@"firstName"],
                                oldObject[@"lastName"]];
    }

    // Add the 'email' property to Realms with a schema version of 0 or 1
    if (oldSchemaVersion < 2) {
      newObject[@"email"] = @"";
    }
  }];
}];

// now that we have called `setSchemaVersion:withMigrationBlock:`, opening an outdated
// Realm will automatically perform the migration and opening the Realm will succeed
[RLMRealm defaultRealm];
RLMRealm.setSchemaVersion(2, forRealmAtPath: RLMRealm.defaultRealmPath(), 
                         withMigrationBlock: { migration, oldSchemaVersion in
  // The enumerateObjects:block: method iterates
  // over every 'Person' object stored in the Realm file
  migration.enumerateObjects(Person.className()) { oldObject, newObject in
    // Add the 'fullName' property only to Realms with a schema version of 0
    if oldSchemaVersion < 1 {
      let firstName = oldObject["firstName"] as String
      let lastName = oldObject["lastName"] as String
      newObject["fullName"] = "\(firstName) \(lastName)"
    }

    // Add the 'email' property to Realms with a schema version of 0 or 1
    if oldSchemaVersion < 2 {
        newObject["email"] = ""
    }
  }
})

// Realm will automatically perform the migration and opening the Realm will succeed
let realm = RLMRealm.defaultRealm()

詳しくは、migration sample app をご覧ください。

線形的なマイグレーション

二人のユーザーがいると考えてください。 JPとTimです。 JPは、よくアプリのアップデートをします。しかし、Timは、いくつかのバージョンをスキップすることがあります。 JPにとっては、全てのバージョンをインストールしてることになるので、段階を踏んで全てのスキーマのアップデートを適用していることになります。 V0からV1, V1からV2のように彼のアプリはアップデートしていきます。 それとは対照的に、Timの場合は、V0からV2といきなりバージョンをすっ飛ばす恐れがあります。 マイグレーションブロックの中では、たとえどのバージョンのスキーマから始めたとしても、ネストしていない if (oldSchemaVersion < X) で必ず必要なアップデート処理は行われなければなりません。

また、他のケースで、ユーザーがバージョンのスキップをしてアプリのアップデートをしたとします。もしあなたが、”email” プロパティをバージョン2で一度、削除しバージョン3で再び定義したとします。 そして、ユーザーはバージョン1からバージョン3へと、バージョン2をスキップしてアップデートした場合、Realmは、コードのスキーマとディスクのスキーマに実質、違いはないので、”email” プロパティが一度、消されたことを自動的に判別することはできません。これは、TimのPersonクラスで起こりうることで、V1でのプロパティの値をV3のプロパティの値として保持される可能性があります。 V1とV3の間で、ストレージのスキーマが変わっていないため、このことは問題にならないかもしれません。 しかし、これを避けるため、if (oldSchemaVersion < 3) の中で、データセットがV3のものであることを保証するために、一度nilを代入することをオススメします。

次のステップ

Realmをより深く理解する、次のステップとして、サンプルコードを用意しています。 HAPPY HACKING!! Google グループ Realm で、あなたはいつでもRealmデベロッパーと議論ができます。

開発中の機能

Realmは、現在β版としてリリースされています。バージョン1.0に向けて、機能追加、バグの修正などを行っています。私たちは、次に導入予定のリストを作成しました。 詳しくは、GitHub issues をご覧ください。

CocoaPodsを使ってRealmをインストール(Swift)

CocoaPodsは、まだSwift製ライブラリに対応していません。(詳しくは GitHub issue #2222) SwiftプロジェクトでRealmをご利用される場合は、ここ を参考にしてください。

より詳細なNotification

Realmのデータが変更される毎に、Notification を取得することはできますが、現在は、Notificationからアクション(Add, Remove, Move, Updateなど)を識別することができません。 この機能を近々、追加する予定です。

NSDateについて

NSDate は、一度、Realmに保存されると少数点以下は切り捨てられます。 この問題は、現在修正中です。 詳しくは、 GitHub issue #875 をご覧ください。 それまで、少数点以下の時間を正確に保存したい場合は、 NSTimeInterval(double)型 をお使いください。

RealmObjectのセッター/ゲッターのオーバーライドについて

Realmは、データベースのプロパティと直結させるためにセッター/ゲッターをオーバーライドしています。そのため、Realmモデルクラスで、プロパティのセッター/ゲッターをオーバーライドすることはできません。一時的な解決方法は、ignore properties として宣言することです。こうすることで、セッター/ゲッターのオーバーライドができるようになります。

KVOのサポートについて

現在、KVOはサポートされていませんが、独自の Notification の仕組みがあります。

複数プロセスからのRealmの利用

もちろん、複数のスレッドから同時にRealmファイルへアクセスすることはできますが、シングルプロセスからのみ利用可能です。 これは、主に、iOS8のExtentionsとOSXアプリケーションでの開発に影響します。複数のプロセスで同じRealmファイルを使う場合は、Realmファイルをコピーするか、もしくは、新しいRealmファイルを作成してください。 マルチプロセスサポートは、近々、追加するする予定です。

FAQ

Realmは、どれくらいの大きさですか?

アプリをリリースビルドすると、Realmのサイズは1MBぐらいになります。 現在、配布されているRealmは、ARM, ARM64, x86 のシュミレータに対応しているのとDebug用のシンボルが含まれてるため、著しく大きく(iOS版は、~37MB, OSX版は、~2.4MB)なっています。それらは、ビルド時にXcodeが、自動的に取り除いてくれます。

Realmをプロダクション環境で使うことはできますか?

Realmは、2012年から商業利用がされています。 ご利用される場合は、RealmのObjective‑C/SwiftAPIが、頻繁に変わるものだとお考えの上、Community Feedback を確認しながらお使いください。 機能追加、バグ修正も同様にお考えください。

Realmのデータは、どのように保護すればいいですか?

いくつかの暗号化方法があります。詳しくは、GitHub issues をご覧ください。クロスプラットフォームでの暗号化にも、近々サポートする予定です。

Relamを使うのにお金を払わないといけませんか?

いいえ、Realmは、完全に無料です。商業利用も可能です。

どのようなビジネスプランなのですか?

すでにエンタープライズ向けの商品の販売や、周辺サービスによって収益を得ています。もし現在リリースされているものやrealm-cocoaで更に必要なものがあれば、いつでもメールで、お気軽にご連絡ください。また私たちのビジネスとは関係なく、realm-cocoaはオープンに開発をつづけていき、Apache License 2.0の元にオープンソースで公開し続けます。

“tightdb” や “core” という文字をコードの中で見たのですが、これは何ですか?

TightDBというのは、C++で実装されたストレージエンジンの名前です。現在、オープンソースではありませんが、Apache License 2.0として公開することを検討中です。 バイナリリリースは、Realm Core (TightDB) Binary License として利用可能です。

</div>