古いバージョンのドキュメントを表示しています。
最新版のドキュメントはこちらからご覧になれます。
SwiftでRealmを利用する場合は、Realm Swiftを使うこともご検討ください。よりSwiftらしい記述ができます。 Realm Objective‑CとRealm Swiftを併用することはサポートしていません。
Realm はアプリケーションのモデル層を効率的に安全で迅速な方法で記述することができます。 下記の例をご覧ください:
// 通常のObjective‑Cのクラスと同じように定義します
@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@end
RLM_ARRAY_TYPE(Dog)
@interface Person : RLMObject
@property NSString *name;
@property NSData *picture;
@property RLMArray<Dog *><Dog> *dogs;
@end
// 通常のObjective‑Cのオブジェクトと同じように扱えます
Dog *mydog = [[Dog alloc] init];
mydog.name = @"Rex";
NSLog(@"Name of dog: %@", mydog.name);
// データを永続化するのはとても簡単です
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
[realm addObject:mydog];
}];
// バックグラウンドスレッドで検索を実行する例です
dispatch_async(dispatch_queue_create("background", 0), ^{
RLMResults<Dog *> *r = [Dog objectsWhere:@"age > 8"];
});
はじめに
Realm をダウンロード ソースコードはGitHubにあります。
必要条件
- iOS 7以降、またはOS X 10.9以降、およびwatchOS。
- Xcode 6以降。
- Objective‑CとSwift 1.2、Swift 2.0をサポートしています。
インストール
注意: Dynamic frameworkはiOS 7では利用できません。iOS 7をサポートする場合は“Static Framework”の説明をご覧ください。
- こちらから最新版のRealmをダウンロードしてzipファイルを展開してください。
- Xcodeでプロジェクトをクリックしプロジェクト設定の“General”タブを表示します。展開したフォルダの中にある
ios/
、watchos/
、またはosx/
フォルダにあるdynamic/
フォルダからRealm.framework
を“Embedded Binaries”にドラッグ&ドロップしてください。このとき、Copy items if neededにチェックが入ってることを確認してからFinishをクリックしてください。 - ユニットテストのターゲットを選択して“Build Settings”タブの“Framework Search Paths”に
Realm.framework
の親フォルダのパスを追加してください。 - Swiftで利用する場合は、さらに
Swift
フォルダにあるRLMSupport.swift
も同様にファイルナビゲータエリアにドラッグ&ドロップし、Copy items if neededをチェックして追加してください。 - iOSのプロジェクトで利用する場合は、アプリケーションのターゲットの“Build Phases”タブで新しく“Run Script Phase”を追加し、以下のスクリプトをそのままコピー&ペーストしてください。
bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh"
この手順はアプリケーションを申請する際のiTunes Connectの不具合を回避するために必要です。
- CococaPods 0.37.1以降をインストールしてください。
- Podfileのアプリケーションのターゲットに対して
pod 'Realm'
、テストターゲットに対してpod 'Realm/Headers'
と追加してください。 - コマンドラインで
pod install
を実行してください。 - CocoaPodsによって作られた
.xcworkspace
ファイルを開いてください。
- Carthage 0.7.5以降をインストールしてください。
- Cartfileに
github "realm/realm-cocoa"
と追加してください。 - コマンドラインで
carthage update
を実行してください。 - iOS:
Realm.framework
をCarthage/Build/iOS/
ディレクトリからXcodeの“General”タブにある“Linked Frameworks and Libraries”にドラッグしてください。
OS X:Realm.framework
をCarthage/Build/Mac/
ディレクトリからXcodeの“General”タブにある“Embedded Binaries”にドラッグしてください。
watchOS:Realm.framework
をCarthage/Build/watchOS/
ディレクトリからXcodeの“General”タブにある“Embedded Binaries”にドラッグしてください。 -
iOS: アプリケーションのターゲットの“Build Phases”の“+”ボタンをクリックし、以下のような“New Run Script Phase”を追加します。
/usr/local/bin/carthage copy-frameworks
このとき、“Input Files”にはフレームワークのパスを指定します。
例:
$(SRCROOT)/Carthage/Build/iOS/Realm.framework
この手順はアプリケーションを申請する際のiTunes Connectの不具合を回避するために必要です。
- こちらから最新版のRealmをダウンロードしてzipファイルを展開してください。
- 展開したフォルダの中にある
ios/static/
フォルダからRealm.framework
をXcodeのファイルナビゲータエリアにドラッグ&ドロップしてください。このとき、Copy items if neededにチェックが入ってることを確認してからFinishをクリックしてください。 - Xcodeのファイルナビゲータエリアでプロジェクトをクリックし、アプリケーションのターゲットを選択します。Build PhasesタブのLink Binary with Librariesにある“+”ボタンをクリックして、libc++.dylibを追加してください。
- Swiftで利用する場合は、さらに
Swift/
フォルダにあるRLMSupport.swift
も同様にファイルナビゲータエリアにドラッグ&ドロップし、Copy items if neededをチェックして追加してください。
Realm Browser
RealmBrowserは、.realm
データベースを閲覧、編集するMac アプリケーションです。
また、Tools > Generate demo database と選択すると、サンプルデータを含むテスト用のRealmデータベースを作ることができます。
開発中のアプリのRealmファイルがどの場所に格納されているかは、このStackOverflowの回答が参考になります。
Realm BrowserはMac App Store からダウンロードできます。
Xcode Plugin
Xcodeプラグインを使うことでRealmモデルファイルの作成が簡単になります。
このXcodeプラグインをインストールする一番簡単な方法は、Alcatrazで“RealmPlugin”と検索することです。
また、手動でインストールする場合は、最新版のRealmのplugin/RealmPlugin.xcodeproj
をビルドすることでインストールができます。
プラグインをインストール後にXcodeを再起動すると、Xcodeで新しくファイルを作成時(File > New > File...
または⌘N
)にRealm Modelという選択肢が追加されています。
API Reference
Realmで使用できるすべてのクラスとメソッドに関しては、API Referenceをご覧ください。
サンプルコード
最新のRealmのexamples
フォルダにiOSとOS Xそれぞれのサンプルコードがあります。 マイグレーション、UITableViewControllerとの使い方、暗号化やコマンドラインツールなど、さまざまな機能が紹介されています。ぜひ、ご参考にしてください。
ヘルプ
- 使い方に困ったときは、StackOverflowで#realmタグを付けて質問してください。私たちは毎日StackOverflowをチェックしています。
- さらに複雑な問題に対する質問は、こちらの Slackチャットにて聞いてください。Slack上で毎月のオフィスアワーも開催しています。(質問は日本語で構いません)
- バグ報告や機能リクエストについては GitHubのIssuesにご報告ください。
- Community Newsletterに参加することで定期的にRealmに関するTipsやユースケース、ブログの更新やチュートリアルなど、Realm の最新情報が届きます。
モデル
Realmのモデルは、普通ののクラスとして定義できます。RLMObjectまたは既存のモデルクラスのサブクラスを作ることでアプリケーションで使うデータモデルを作成できます。
Realmのモデルオブジェクトは、他のオブジェクトとほとんど同様に機能するので、一般的なクラスと同様にメソッドやプロトコルを追加することができます。
主な制限としては、Realmモデルクラスのインスタンスは他のスレッドに渡すことができません。またプロパティのインスタンス変数に直接アクセスすることはできません。
XcodePluginを利用すると、ファイルメニューの“New File...
”からテンプレートを選択することでRealmモデルクラスを簡単に作成できます。
リレーションシップとネストしたデータ構造は、対象の型のプロパティを持たせるか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 *><Dog> *dogs;
@end
RLM_ARRAY_TYPE(Person) // define RLMArray<Person>
// Implementations
@implementation Dog
@end // none needed
@implementation Person
@end // none needed
Realmはアプリケーションの起動時に、定義されたすべてのモデルクラスを解析しています。まったく使われていないクラスも含めて、すべてのモデルクラスは正しく定義されていなければなりません。
詳しくはRLMObjectをご覧ください。
対応しているデータ型
Realmでは、次に示すデータ型をサポートしています: BOOL
、bool
、int
、NSInteger
、long
、long long
、float
、double
、CGFloat
、NSString
、NSDate
(ミリ秒以下は切り捨てられます)、およびNSData
。
1対1や、1対多のようなリレーションシップのためにRLMArray<Object *><Object>
やRLMObject
のサブクラスのプロパティも定義できます。
Xcode 7以降ではRLMArray
は“Lightweight Generics”をサポートしています。下記に示すのはプロパティ定義の構成要素がそれぞれどのように役に立つかの説明です:
RLMArray
: プロパティの型を示します。<Object *>
: ジェネリクスの型引数です。コンパイル時に間違った型のオブジェクトが格納されるのを防ぎます。<Object>
:RLMArray
が準拠しているプロトコルです。実行時にRealmが型を判別できるようにします。
リレーションシップ(関連)
RLMObjectはプロパティにRLMObjectやRLMArrayを使用して、他のオブジェクトとの関連を定義することができます。
RLMArrayはNSArrayとよく似たAPIを持ち、添え字を使って各要素にアクセスすることもできます。
NSArrayと異なる点は、RLMArrayはRLMObjectのサブクラスのみ格納することができるということです。 詳しくはRLMArrayをご覧ください。
すでにこれまでのステップでPersonモデルを定義しました。もう一つDogモデルを作成し関連を定義してみましょう。
// Dog.h
@interface Dog : RLMObject
@property NSString *name;
@end
1対1のリレーションシップ
モデル間で1対1や1対多の関連を持たせるには、別のRLMObjectモデルクラスを持つプロパティを下記のように定義します:
// Dog.h
@interface Dog : RLMObject
... // other property declarations
@property Person *owner;
@end
このプロパティは以下のように使えます:
Person *jim = [[Person alloc] init];
Dog *rex = [[Dog alloc] init];
rex.owner = jim;
RLMObjectをプロパティとして持つとき、ネストされたプロパティには通常のオブジェクトのプロパティと同じ文法でアクセスできます。上記の例では、rex.owner.address.country
とすると、Realmはネストしたオブジェクトを必要に応じて自動的にフェッチします。
1対多のリレーションシップ
1対多の関連を定義するにはRLMArrayを持つプロパティを定義します。RLMArrayは別のRLMObjectを含み、NSMutableArrayとよく似たAPIを持ちます。
PersonクラスがDogクラスを1対多の関連として持つとします。まず、RLMArray<Dog>
を定義します。 これは下記のように、関連するクラス定義の下に専用のマクロを使って定義します。
// Dog.h
@interface Dog : RLMObject
// ... property declarations
@end
RLM_ARRAY_TYPE(Dog) // RLMArray<Dog>型の定義
これでRLMArray<Dog>
型のプロパティを定義できるようになりました。
// Person.h
@interface Person : RLMObject
... // other property declarations
@property RLMArray<Dog *><Dog> *dogs;
@end
これで、下記のようにしてRLMArray型のプロパティに対してアクセスしたり、オブジェクトを追加したりできるようになりました:
// Jim is owner of Rex and all dogs named "Fido"
RLMResults<Dog> *someDogs = [Dog objectsWhere:@"name contains 'Fido'"];
[jim.dogs addObjects:someDogs];
[jim.dogs addObject:rex];
逆方向の関連
逆方向の関連(バックリンクとも言われます)を設定することで、関連元のオブジェクトを特定のプロパティを使って取得することができます。 例えばDog
クラスのオブジェクトから-linkingObjectsOfClass:forProperty:
を使うとそのオブジェクトに関連する特定のモデルのオブジェクトをすべて取得することができます。この方法を使うと、Dog
クラスにowners
という読み取り専用のプロパティを追加するというパターンをシンプルに実装できます:
@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@property (readonly) NSArray *owners; // Realm doesn't persist this property because it is readonly
@end
@implementation Dog
// Define "owners" as the inverse relationship to Person.dogs
- (NSArray *)owners {
return [self linkingObjectsOfClass:@"Person" forProperty:@"dogs"];
}
@end
プロパティ属性
Realmではnonatomic
、atomic
、strong
、copy
、weak
のようなObjective‑Cのプロパティ属性は無視されます。 Realmの内部では独自の最適化された仕組みが使われます。 そのため、混乱を避けるために、Realmモデルクラスを定義するときには一切のプロパティ属性を付けないことを推奨しています。
ただし、プロパティ属性を付けた場合はRLMObject
がRealmに保存されるまでは有効です。 setter
またはgetter
属性を利用してセッター/ゲッターメソッドの名前を変えた場合は、RLMObject
がRealmに追加されているどうかに関わらず、変更後の名前を使うことができます。
インデックス付きプロパティ
クラスメソッドの+indexedProperties
をオーバーライドすることで、インデックスに追加するプロパティを指定できます:
@interface Book : RLMObject
@property float price;
@property NSString *title;
@end
@implementation Book
+ (NSArray *)indexedProperties {
return @[@"title"];
}
@end
初期値
クラスメソッドの+defaultPropertyValues
をオーバーライドすることで、プロパティの初期値を指定できます。Swiftでは標準でプロパティに初期値を設定する方法が提供されているため、defaultPropertyValues()
ではなく、標準の方法を使用します。
@interface Book : RLMObject
@property float price;
@property NSString *title;
@end
@implementation Book
+ (NSDictionary *)defaultPropertyValues {
return @{@"price" : @0, @"title": @""};
}
@end
プライマリキー
クラスメソッドの+primaryKey
をオーバーライドすることで、そのモデルのプライマリキーを指定できます。プライマリキーを使うと、オブジェクトを効率的に検索・更新することができ、一意性を保つこともできます。
@interface Person : RLMObject
@property NSInteger id;
@property NSString *name;
@end
@implementation Person
+ (NSString *)primaryKey {
return @"id";
}
@end
保存しないプロパティ
クラスメソッドのignoredProperties
をオーバーライドすることで、Realmに保存しないプロパティを指定することができます。
保存しないプロパティについてはRealmはプロパティの操作に介入しません。つまり普通のプロパティとしてインスタンス変数に値は格納され、getter/setterメソッドをオーバーライドすることも自由にできます。
@interface Person : RLMObject
@property NSInteger tmpID;
@property (readonly) NSString *name; // 読み取り専用プロパティは自動的に保存しないプロパティとして扱われます
@property NSString *firstName;
@property NSString *lastName;
@end
@implementation Person
+ (NSArray *)ignoredProperties {
return @[@"tmpID"];
}
- (NSString *)name {
return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}
@end
書き込み
Realmへのオブジェクトの追加、変更、削除は、トランザクションの内部で行う必要があります。
Realmモデルクラスは、通常のオブジェクトと同じようにインスタンス化し、使うことができます。
スレッド間でデータを共有したり、アプリキーションの再起動時に以前のデータを利用するには、Realmにデータを保存しなければなりません。これらの操作は、トランザクションの中で行う必要があります。
オブジェクトの生成
RLMObjectのサブクラスとして定義したモデルをインスタンス化して、新しいオブジェクトとしてRealmに保存します。
次のような簡単なモデルを考えます:
// Dog model
@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@end
// Implementation
@implementation Dog
@end
オブジェクトを作成するにはいくつかの方法があります:
// (1) Dogクラスのオブジェクトを作成し、プロパティに値をセットする
Dog *myDog = [[Dog alloc] init];
myDog.name = @"Rex";
myDog.age = 10;
// (2) NSDictionaryの値を使ってDogクラスのオブジェクトを作成する
Dog *myOtherDog = [[Dog alloc] initWithValue:@{@"name" : @"Pluto", @"age" : @3}];
// (3) NSArrayの値を使ってDogクラスのオブジェクトを作成する
Dog *myThirdDog = [[Dog alloc] initWithValue:@[@"Pluto", @3]];
- Objective‑Cにおけるalloc-initや、Swiftにおける指定イニシャライザを使うのはオブジェクトを作るもっともわかりやすい方法です。 すべてのプロパティの値を、オブジェクトがRealmに追加される前にセットする必要があるので注意してください。
- 適切なキーと値の組み合わせを持つディクショナリからオブジェクトを生成することもできます。
- 最後の方法は配列からオブジェクトを作る方法です。配列の各要素は、生成するモデルのプロパティと同じ順序で並んでいる必要があります。
ネストしたオブジェクト
RLMObjectのサブクラス、またはRLMArrayのプロパティを持つモデルオブジェクトは、ネストした配列やディクショナリを使って再帰的にオブジェクトをセットすることができます。 そのとき、配列またはディクショナリを使って、下記のように簡単にそれぞれのオブジェクトを生成することができます:
// すでに存在するオブジェクトを渡す代わりに...
Person *aPerson = [[Person alloc] initWithValue:@[@"Jane", @30, @[aDog, anotherDog]]];
// ...配列を使ってその場で値を渡すことができます
Person *aPerson = [[Person alloc] initWithValue:@[@"Jane", @30, @[@[@"Buster", @5],
@[@"Buddy", @6]]]];
この方法は、ネストした配列やディクショナリがどのような組み合わせであっても動作します。この場合のRLMArrayはRLMObject
のオブジェクトだけを格納できることに注意してください。NSString
のような他の基本的な型は格納できません。
オブジェクトの追加
オブジェクトをRealmに追加するには次のようにします:
// オブジェクトを作成する
Person *author = [[Person alloc] init];
author.name = @"David Foster Wallace";
// デフォルトRealmを取得する
RLMRealm *realm = [RLMRealm defaultRealm];
// Realmの取得はスレッドごとに1度だけ必要になります
// トランザクションを開始して、オブジェクトをRealmに追加する
[realm beginWriteTransaction];
[realm addObject:author];
[realm commitWriteTransaction];
オブジェクトをRealmに追加した後も、続けて使用することができます。そして、オブジェクトに対するすべての変更が保存されます。(オブジェクトを変更は、トランザクションの中で行わなければなりません)。別のスレッドで、同じRealmに保存されているオブジェクトに変更があった場合は、トランザクションが完了した時点ですべての変更が適用されます。
同時に発生した書き込み処理は、互いの書き込み処理をブロックします。 これは類似の他のデータベースでも同様で、よく使われるベストプラクティスとして、書き込み処理を別のスレッドに分けることを推奨します。
RealmはMVCCアーキテクチャーを採用しているので、書き込み処理の最中でも読み込み処理をブロックすることはありません。同時に複数のスレッドから書き込みをするのでなければ、大きな単位でトランザクションを使いましょう。細かいトランザクションを使うよりこの特性を活かすことができます。
詳しくは、RLMRealm and RLMObjectをご覧ください。
オブジェクトの更新
オブジェクトを更新するには、トランザクションの中でプロパティをセットします。
// トランザクションを開始して、オブジェクトを更新する
[realm beginWriteTransaction];
author.name = @"Thomas Pynchon";
[realm commitWriteTransaction];
プライマリキーを使ってオブジェクトを更新する
モデルにプライマリキーを指定しているなら、+[RLMObject createOrUpdateInRealm:withValue:]
を使って、オブジェクトがすでに存在する場合は更新、存在しない場合は新しく追加というように、追加または更新を一度に行うことができます。
// 以前に保存したものと同じプライマリキーを持つBookオブジェクトを作成する
Book *cheeseBook = [[Book alloc] init];
cheeseBook.title = @"Cheese recipes";
cheeseBook.price = @9000;
cheeseBook.id = @1;
// id = 1のBookオブジェクトの値を更新する
[realm beginWriteTransaction];
[Book createOrUpdateInRealm:realm withValue:cheeseBook];
[realm commitWriteTransaction];
ここでid = 1
のBookオブジェクトがRealmに保存されていない場合は、新しいBookオブジェクトが追加されます。
オブジェクトのプロパティを部分的に更新するには、下記のように更新したいプロパティの値とプライマリキーだけが含まれたディクショナリを渡します。
// プライマリキーが`1`のBookオブジェクトがすでにあるとき、
[realm beginWriteTransaction];
[Book createOrUpdateInRealm:realm withValue:@{@"id": @1, @"price": @9000.0f}];
// タイトルはそのままで値段のプロパティだけを更新することができます。
[realm commitWriteTransaction];
キー値コーディング
RLMObject、RLMResult、RLMArray クラスはいずれも キー値コーディング(KVC)に対応しています。 実行時にアップデートするプロパティが決定する場合に使用すると便利です。
また、キー値コーディングをコレクションオブジェクトに対して使用すると、多数のオブジェクトをループしてインスタンス化することが避けられるので、一括の更新を非常に効率的に行うことができます。
RLMResults *persons = [Person allObjects];
[[RLMRealm defaultRealm] transactionWithBlock:^{
[[persons firstObject] setValue:@YES forKeyPath:@"isFirst"];
// すべてのPersonオブジェクトのプロパティを"Earth"に更新します
[persons setValue:@"Earth" forKeyPath:@"planet"];
}];
オブジェクトの削除
オブジェクトを削除するには、トランザクションの中で削除したいオブジェクトを-[RLMRealm deleteObject:]
メソッドに渡します。
Book *cheeseBook = ... // 保存されているBookオブジェクトを取得して、
// トランザクションを開始してオブジェクトを削除します
[realm beginWriteTransaction];
[realm deleteObject:cheeseBook];
[realm commitWriteTransaction];
下記のように、Realmに保存されてるオブジェクトをすべて削除することもできます。 ディスクスペースを効率的に再利用するために、Realmファイルのサイズはそのまま維持されることに注意してください。
// Realmに保存されているすべてのオブジェクトを削除します。
[realm beginWriteTransaction];
[realm deleteAllObjects];
[realm commitWriteTransaction];
クエリ
クエリを実行するとRLMObjectを含んだRLMResults
オブジェクトが結果として返ってきます。 RLMResultsは、NSArrayに非常によく似たようなAPIを持ち、添え字を使ってオブジェクトにアクセスすることもできます。 NSArrayと違うのは、RLMResultsは、RLMObjectのサブクラスのみを含むことができるという点です。
Realmにおけるすべてのクエリとプロパティへのアクセスを含むは遅延ロードされます。プロパティにアクセスした時に、初めてデータが読み込まれます。
クエリを実行したときに返ってくる結果は、コピーではありません。トランザクションを使ってそのデータを変更した場合、ディスクのデータを直接変更したことになります。 同様に、RLMResultsに含まれるRLMObjectから、関連のオブジェクトを次々とたどりながら取得することもできます。
型を使ったオブジェクトの取得
もっとも基本的なオブジェクトを取得する方法は+[RLMObject allObjects]
メソッドを使うことです。 このメソッドは、デフォルトRealmに保存されているRLMObjectオブジェクトのうち、指定したクラスのすべてのインスタンスを含むRLMResultsを返します。
// デフォルトRealmに対してクエリを実行します
RLMResults *dogs = [Dog allObjects]; // デフォルトRealmから、すべてのDogオブジェクトを取得します
// 特定のRealmに対してクエリを実行します
RLMRealm *petsRealm = [RLMRealm realmWithPath:@"pets.realm"]; // 特定のRealmを取得します
RLMResults *otherDogs = [Dog allObjectsInRealm:petsRealm]; // 特定のRealmから、すべてのDogオブジェクトを取得します
検索条件を指定する
普段からNSPredicateの扱いに慣れているなら、Realmにおけるオブジェクトの検索方法を知っているも同然です。
RLMObjects、RLMRealm、RLMArray、RLMResultsはすべて特定のRLMObjectオブジェクトをNSPredicateを使って検索するためのメソッドをサポートしています。 NSArrayを検索するときと同じように、直接NSPredicateオブジェクトを渡す、条件部分だけを文字列で渡す、などの方法が利用できます。
下記の例は、[RLMObject objectsWhere:]
メソッドを使って、Dogクラスのcolor = "tan"
かつ、nameの値がB
から始まるという条件に合致するDog クラスのインスタンスをデフォルトRealmから取得します。
// 文字列で検索条件を指定します
RLMResults *tanDogs = [Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"];
// NSPredicateを使って検索条件を指定します
NSPredicate *pred = [NSPredicate predicateWithFormat:@"color = %@ AND name BEGINSWITH %@",
@"tan", @"B"];
tanDogs = [Dog objectsWithPredicate:pred];
詳しくは、AppleのPredicates Programming GuideやRealmが提供しているNSPredicateチートシートをご覧ください。Realmでは多数のNSPredicate構文をサポートしています。
- 比較演算子はプロパティ名と定数に対して使用できます。左辺と右辺のうち少なくとも一方はプロパティ名でなければなりません。
-
比較演算子として==、<=、<、>=、>、!=、BETWEENがint、long、long long、float、double、NSDateに対して使用できます。
(例)age == 45
-
同一性の比較==、!=
(例)[Employee objectsWhere:@”company == %@”, company]
- bool型のプロパティに対しては比較演算子として==と!=が使用できます。
-
NSStringとNSData型のプロパティに対しては、==、!=、BEGINSWITH、CONTAINS、ENDSWITH演算子が使用できます。
(例)name CONTAINS ‘Ja’
- 文字列に対して大文字小文字を無視して比較するには、name CONTAINS[c] ‘Ja’のようにします。大文字小文字として扱われるのはアルファベットの”A-Z”および”a-z”であることに注意してください。
-
論理演算子として“AND”、“OR”、“NOT”が使用できます。
(例)name BEGINSWITH ‘J’ AND age >= 32
-
いずれかの条件と一致するかどうか: IN
(例)name IN {‘Lisa’, ‘Spike’, ‘Hachi’}
-
nilとの比較: ==, !=
(例)
[Company objectsWhere:@"ceo == nil"]
。
nilとの比較は関連に対してのみ機能します。この例ではceo
プロパティがCompany
の関連として定義されています。
-
いずれかの要素が条件と一致するかどうか: ANY
(例)ANY student.age < 21
-
集計関数はサポートしていませんが、BETWEEN演算子は使えます。
(例)
[Person objectsWhere:@"age BETWEEN %@", @[42, 43]]
.
詳しくは、[RLMObject objectsWhere:]
をご覧ください。
並べ替え
RLMResultsは、1つあるいは複数のプロパティの値を使って並べ替えることができます。下記は、先ほどのDogオブジェクトを検索した結果を、nameプロパティのアルファベット順で並べ替える例です。
// color = 'tan'かつ名前が"B"から始まるDogオブジェクトを、名前の昇順で取得します
RLMResults *sortedDogs = [[Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"]
sortedResultsUsingProperty:@"name" ascending:YES];
詳しくは、[RLMObject objectsWhere:]
and [RLMResults sortedResultsUsingProperty:ascending:]
をご覧ください。
クエリの連鎖
他のデータベースと比較してRealmを使う利点として、非常に小さなオーバーヘッドで、連鎖したクエリを実行できる点が挙げられます。 例えば、color = tan
かつ、name
がB
からはじまるDogオブジェクトを検索したい場合、以下のように連鎖的にメソッドを呼び出すことができます。
RLMResults *tanDogs = [Dog objectsWhere:@"color = 'tan'"];
RLMResults *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH 'B'"];
Realmについて
デフォルトRealm
すでにお気づきだと思いますが、これまで[RLMRealm defaultRealm]
メソッドを呼ぶことで、変数realm
を初期化してきました。
このメソッドは、各アプリケーションのDocumentsフォルダに作られた”default.realm”ファイルのRLMRealmオブジェクトを返します。
Realmが持つたくさんのメソッドには、Realmインスンタスを引数として受け取るものと、自動的にデフォルトRealmが使われるものの両方があります。例えば、[RLMObject allObjects]
は、[RLMObject allObjectsInRealm:[RLMRealm defaultRealm]]
と同じ意味になります。
Realm Configuration
Realmファイルの保存場所などの、Realmに対する設定をカスタマイズするには、RLMRealmConfigurationオブジェクトを使います。
設定オブジェクトはRealmのインスタンスを取得する際に[RLMRealm realmWithConfiguration:config error:&err]
メソッドに渡すこともできますし、[RLMRealmConfiguration setDefaultConfiguration:config]
メソッドを利用して、デフォルトRealmの設定とすることもできます。
例えば、次のようなアプリケーションを考えます。Web APIによるログイン認証があり、複数のアカウントを切り替えることができるとします。
その場合、アカウントごとにデフォルトRealmの保存場所を設定することで、それぞれのアカウントで別のRealmファイルを使い分けることができます。
+ (void)setDefaultRealmForUser:(NSString *)username {
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 保存先のディレクトリはデフォルトのままで、ファイル名をユーザー名を使うように変更します
config.path = [[[config.path stringByDeletingLastPathComponent]
stringByAppendingPathComponent:username]
stringByAppendingPathExtension:@"realm"];
// ConfigurationオブジェクトをデフォルトRealmで使用するように設定します
[RLMRealmConfiguration setDefaultConfiguration:config];
}
デフォルト以外のRealm
複数のRealmファイルを別の場所に保存して使い分けることができると便利です。例えば、事前に用意した組み込み済みデータを、メインのデータファイルとは別に読み込み専用のRealmとして利用するなどです。
下記のコードは、アプリケーションに同梱された別のRealmファイルからデータを読み込む例です。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// アプリケーションバンドルのパスを設定します
config.path = [[NSBundle mainBundle] pathForResource:@"MyBundledData" ofType:@"realm"];
// アプリケーションバンドルは書き込み不可なので、読み込み専用に設定します。
config.readOnly = YES;
// RealmをConfigurationオブジェクト使って作成します
RLMRealm *realm = [RLMRealm realmWithConfiguration:config];
// バンドルのRealmからデータを取得します
RLMResults<Dog *> *dogs = [Dog objectsInRealm:realm where:@"age > 5"];
Realmを初期化するときに指定するファイルパスは、書き込み可能な場所を指している必要があります。
書き込み可能であるもっとも普通な保存場所は、iOSでは”Documents”フォルダ、OS Xでは”Application Support”フォルダです。
AppleのiOS Data Storage Guidelinesによると、<Application_Home>/Library/Caches
フォルダに保存することが推奨されています。
In-Memory Realm
通常は、Realmはディスクにデータを保存しますが、path
を指定する代わりにinMemoryIdentifier
を利用することで、データをメモリ上のみに保持するRealmオブジェクトを作ることができます。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.inMemoryIdentifier = @"MyInMemoryRealm";
RLMRealm *realm = [RLMRealm realmWithConfiguration:config];
In-memory Realmでは、データをディスクに保存しないのでアプリケーションを終了するとデータは消えてしまいます。それ以外のクエリ、リレーションシップ、スレッドセーフなどの機能はすべて通常のRealmと同様に利用することができます。
ディスクへの読み書きによるオーバーヘッドの無い、柔軟なデータアクセス機能が必要なときに有効です。
In-memory Realmはプロセス間通信などに利用するため、一時ディレクトリにいくつかのファイルを作成します。
メモリ不足によりOSがスワップを要求したとき以外は、何のデータも書き込まれません。
注意: スコープを外れ、In-Memory Realmインスタンスへの参照がすべて無くなると、そのRealm内に保存されている、すべてのデータは解放されます。
アプリケーションの起動中は、In-MemoryなRealmインスタンスへの強参照を常に保持しておく必要があります。
複数スレッド間でRealmを使う
同じRealmファイルを複数のスレッドから使う場合は、各スレッドでRealmインスタンスをそれぞれ生成する必要があります。 同じ設定によって作られたRealmインスタンスは、ディスク上で同じものを指します。
複数のスレッド間で、Realmインスタンスを共有することはサポートされていません。 同じRealmファイルを指すRealmのインスタンスのrealdOnly
プロパティは同じでなければなりません。(すべてreadwriteまたはすべてreadonlyである必要があります)
Realmは、大量のデータを追加するときには、一つのトランザクション中に複数の一括更新をすることにより、非常に効率よく動作します。
また、メインスレッドをブロックすることを避けるためGrand Central Dispatchを使い、トランザクションをバックグラウンドで実行することができます。
RLMRealmオブジェクトはスレッドセーフではないため、複数のスレッド間で共有することができません。それぞれのスレッド/dispatch_queueでRLMRealmオブジェクトを生成する必要があります。
下記は、バックグランド処理で100万個のオブジェクトを追加する例です。
dispatch_async(queue, ^{
@autoreleasepool {
// このスレッドで使うRealmインスタンスを取得します
RLMRealm *realm = [RLMRealm defaultRealm];
// トランザクションを開始して、
// 1000件単位で書き込みます
for (NSInteger idx1 = 0; idx1 < 1000; idx1++) {
[realm beginWriteTransaction];
// ディクショナリからオブジェクトを作成する場合は、プロパティの順序を気にする必要はありません
for (NSInteger idx2 = 0; idx2 < 1000; idx2++) {
[Person createInRealm:realm
withValue:@{@"name" : [self randomString],
@"birthdate" : [self randomDate]}];
}
// トランザクションを込みとして、
// 他のスレッドのRealmからデータを利用できるようにします。
[realm commitWriteTransaction];
}
}
});
Realm間のオブジェクトのコピー
Realmに保存されたオブジェクトを他のRealmへコピーするには、+[RLMObject createInRealm:withValue:]
メソッドにコピー元のオブジェクトを引数として渡します。 例えば、[MyRLMObjectSubclass createInRealm:otherRealm withValue:originalObjectInstance]
のように使います。
Realmファイルの探し方
アプリケーション内のRealmファイルの場所がわからないときは、このStackOverflowの回答を参考にしてください。
初期データとしてRealmをアプリケーションにバンドルする
初回起動を素早くするためなどに、アプリケーションに初期データを組み込むことはよくあります。 下記はRealmを初期データとしてバンドルする例です:
- はじめに、初期データの入ったRealmを用意します。 リリースする時と同じデータモデルを定義し、データを入れます。Realmファイルは、クロスプラットフォームで利用可能ですので、データの作成はOS Xや、iOSシミュレータ上で行っても問題ありません。(JSONImportの例をご覧ください)
- 初期データを入れるコードの終わりに、Realmファイルをコピーをするメソッドを使用し、Realm ファイルのサイズを最適化してください(
-[RLMRealm writeCopyToPath:error:]
をご覧ください)。 このメソッドを使ってRealmファイルをコピーすると、Realmのファイルサイズを小さくでき、最終的にアプリケーションのサイズが軽くなるのでユーザが速くダウンロードできます。 - コピーされたRealmファイルを、Xcodeのプロジェクトナビゲーターにドラッグ&ドロップします。
- プロジェクト設定のBuild Phaseタブで、”Copy Bundle Resources”にRealmファイルを追加します。
- この時点で、追加したRealmファイルにアプリケーションからアクセスできるようになります。
[[NSBundle mainBundle] pathForResource:ofType:]
メソッドを使って、ファイルパスを取得します。 -
こうして、
[RLMRealm realmWithPath:readOnly:error:]
メソッドで読み込み専用のRealmオブジェクトを作ることもできますし、その初期データを含んだRealmに書き込みたい場合は、[[NSFileManager defaultManager] copyItemAtPath:toPath:error:]
メソッドを使って、アプリケーションのDocuments
ディレクトリにRealmファイルをコピーしてから、[RLMRealm realmWithPath:]
を使って、Realmオブジェクトを作ります。 - 同梱したRealmデータファイルが固定のデータのみを含んでいて、変更する必要が無いのであれば、RLMRealmConfigurationで
readOnly = true
を指定して読み込み専用とし、直接バンドル内のファイルを開くことができます。 いっぽう、初期データを変更する必要があるなら、バンドルからドキュメントディレクトリなどにRealmファイルを[[NSFileManager defaultManager] copyItemAtPath:toPath:error:]
メソッドでコピーしてから利用します。
Realmファイルを初期データとして組み込む例として、マイグレーションのサンプルコードを参考にしてください。
モデル定義のサブセット
各Realmファイルで保存されるモデルの定義を使い分けたいことがあります。
例えば、内部でRealmを利用する異なるコンポーネントを、2つのチームにより開発しているとします。
その場合、自分の開発しているコンポーネントに関係のないモデルに関するマイグレーション処理はやりたくないことでしょう。
下記のように、RLMRealmConfigurationオブジェクトのobjectClasses
プロパティを用いて、それぞれのRealmに保存されるモデルクラスを制限することができます。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.objectClasses = @[MyClass.class, MyOtherClass.class];
RLMRealm *realm = [RLMRealm realmWithConfiguration:config];
通知
他のスレッドでトランザクションが完了するたびに、残りのRealmインスタンスに対して通知が送られます。 通知を受け取るには、下記のようにコールバックをブロックとして登録します。
// Realmの通知を監視するように登録します
self.token = [realm addNotificationBlock:^(NSString *note, RLMRealm * realm) {
[myViewController updateUI];
}];
戻り値の“Notificaiton Token”が保持されている限り、通知は有効です。 更新の通知を登録したクラスの“Notificaiton Token”の強参照を保持しておく必要があります。 “Notificaiton Token”が解放されると、登録された通知は自動的に解除されます。
詳しくは、[RLMRealm addNotificationBlock:]
and [RLMRealm removeNotificationBlock:]
をご覧ください。
マイグレーション
データベースを使ってる場合、時間が経つにつれ、データモデルは変更されていくものです。 Realmでのデータモデルは、シンプルなインターフェースで定義されていますので、インターフェースに変更を加えるだけで、簡単にデータモデルを変えられます。 例えば、以下のPerson
モデルについて考えてみてください。
@interface Person : RLMObject
@property NSString *firstName;
@property NSString *lastName;
@property int age;
@end
ここで、firstName
とlastName
を一つにして、fullName
プロパティが必要になったとします。 そこで以下のような単純な変更をインターフェースに加えることにします。
@interface Person : RLMObject
@property NSString *fullName;
@property int age;
@end
ここでのポイントは、もし以前に前のデータモデルでのデータが保存されている場合、新しく定義し直したデータモデルとディスクに保存されている古いデータモデルとの間に不整合が生じてしまいます。 マイグレーションを実行せずにRealmを使おうとすると、例外が発生します。
マイグレーションを実行する
マイグレーション処理は、RLMRealmConfiguration.migrationBlock
を利用して定義します。
スキーマのバージョンはRLMRealmConfiguration.schemaVersion
を用いて設定します。
マイグレーションブロックの中には、すべての古いデータモデルから新しいデータモデルへ移行させるためのロジックが書かれていなければなりません。
上記の設定オブジェクトを用いてRLMRealmオブジェクトが作られたときに、スキーマバージョンが異なっていればマイグレーション処理が適用されます。
たとえば、上記のPerson
クラスのマイグレーションについて考えてみましょう。 最低限必要なマイグレーション処理は、以下のようなものです。
// [AppDelegate didFinishLaunchingWithOptions:]の中に書きます
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
// 新しいスキーマバージョンを設定します。以前のバージョンより大きくなければなりません。
// (スキーマバージョンを設定したことがなければ、最初は0が設定されています)
config.schemaVersion = 1;
// マイグレーション処理を記述します。古いスキーマバージョンのRealmを開こうとすると
// 自動的にマイグレーションが実行されます。
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// 最初のマイグレーションの場合、`oldSchemaVersion`は0です
if (oldSchemaVersion < 1) {
// 何もする必要はありません!
// Realmは自動的に新しく追加されたプロパティと、削除されたプロパティを認識します。
// そしてディスク上のスキーマを自動的にアップデートします。
}
};
// Tell Realm to use this new configuration object for the default Realm
[RLMRealmConfiguration setDefaultConfiguration:config];
// Now that we've told Realm how to handle the schema change, opening the file
// will automatically perform the migration
[RLMRealm defaultRealm];
Realmによって自動的にスキーマが更新されることを示すために、ここでは空のブロックでマイグレーションを実行しています。
これは最低限のマイグレーションですが、おそらく何かデータを新しいプロパティ(ここではfullName
)に入れるために、ここに処理を記述すると思います。
マイグレーションブロックの中では、特定の型の列挙を行うために[RLMMigration enumerateObjects:block:]
を呼ぶことができます。
下記では、必要なマイグレーションロジックを適用しています。 変数oldObject
を使って既にあるデータにアクセスし、新しく更新するデータには変数newObject
を使ってアクセスしています。
// [AppDelegate didFinishLaunchingWithOptions:]の中に書きます
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 1;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// 最初のマイグレーションの場合、`oldSchemaVersion`は0です
if (oldSchemaVersion < 1) {
// enumerateObjects:block:メソッドで保存されているすべての
// Personオブジェクトを列挙します
[migration enumerateObjects:Person.className
block:^(RLMObject *oldObject, RLMObject *newObject) {
// firstNameとlastNameをfullNameプロパティに結合します
newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",
oldObject[@"firstName"],
oldObject[@"lastName"]];
}];
}
};
[RLMRealmConfiguration setDefaultConfiguration:config];
一度マイグレーション処理が適用されると、その後は通常どおりにRealmとRealmオブジェクトが使用できます。
バージョンの追加方法
Person
クラスについて、以前に異なる2つのデータモデルをとっていた場合を考えてみましょう。
// v0
@interface Person : RLMObject
@property NSString *firstName;
@property NSString *lastName;
@property int age;
@end
// v1
@interface Person : RLMObject
@property NSString *fullName; // 追加したプロパティ
@property int age;
@end
// v2
@interface Person : RLMObject
@property NSString *fullName;
@property NSString *email; // 追加したプロパティ
@property int age;
@end
ここでのマイグレーションブロックの中に書くロジックは、下記のようになります。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 2;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
// enumerateObjects:block:メソッドで保存されているすべての
// Personオブジェクトを列挙します
[migration enumerateObjects:Person.className
block:^(RLMObject *oldObject, RLMObject *newObject) {
// スキーマバージョンが0のときだけ、'fullName'プロパティを追加します
if (oldSchemaVersion < 1) {
newObject[@"fullName"] = [NSString stringWithFormat:@"%@ %@",
oldObject[@"firstName"],
oldObject[@"lastName"]];
}
// スキーマバージョンが0または1のとき、'email'プロパティを追加します
if (oldSchemaVersion < 2) {
newObject[@"email"] = @"";
}
}];
};
[RLMRealmConfiguration setDefaultConfiguration:config];
// スキーマバージョンを更新して、マイグレーションブロックを追加したので、
// 古いスキーマバージョンのRealmを開こうとすると
// Realmは自動的にマイグレーションを実行し、成功したらRealmを開きます。
[RLMRealm defaultRealm];
完全なマイグレーション処理のコードについては、マイグレーション処理のサンプルコードをご覧ください。
複数世代のマイグレーション
二人のユーザーがいると考えてください。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 クラスで起こりうることで、バージョン1時点のプロパティの値をバージョン3バージョンのプロパティの値として保持される可能性があります。
このことは、内部的に”email”プロパティの表現形式を変えるなどしていなければ、問題にならないかもしれません(ISO形式のメールアドレスから独自の形式にするなどです。)。
しかし、問題を避けるためif (oldSchemaVersion < 3)
の中で、データセットがバージョン3バージョンのものであることを保証するために、一度nilを代入することを推奨します。
暗号化
Realmの暗号化APIはiOS、OS XおよびWatchKitで利用できます。ただし、watchOSでは利用できません。
なぜなら、暗号化の仕組みとして使用している<mach/mach.h>
と<mach/exc.h>
のAPIが__WATCHOS_PROHIBITED
となっているためwatchOSでは利用できないからです。
Appleに不具合として報告しています: rdar://22063654。
Please take note of the Export Compliance section of our LICENSE, as it places restrictions against the usage of Realm if you are located in countries with an export restriction or embargo from the United States.
iOSでは、標準のNSFileProtection
APIを使用することで、ほんのわずかのオーバーヘッドでRealmファイルを暗号化することができます。ただし、この方法でファイルを保護する場合、2つの注意点があります。
1) 他のプラットフォームで利用できなくなります。(NSFileProtection
はiOSでのみ有効な機能です)
2) パスコードロックを使用していないデバイスでは、Realmファイルは暗号化されません。
これらの制限を避けるために(もしくはOS Xアプリケーションで利用する場合は)、Realmの提供する暗号化機能を使用することを推奨します。
Realmには64バイトの暗号化キーを用いてAES-256とSHA-2暗号化方式でデータベースファイルを暗号化する機能があります。
// ランダムな暗号化キーを生成します
NSMutableData *key = [NSMutableData dataWithLength:64];
SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes);
// 暗号化されたRealmファイルを開きます
RLMRealmConfiguration *config = [RLMRealm defaultConfiguration];
config.encryptionKey = key;
NSError *error;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (!realm) {
// もし暗号化キーが間違っている場合、`error`オブジェクトは"invalid database"を示します
NSLog(@"Error opening realm: %@", error);
return;
}
// 使い方は暗号化なしのRealmと変わりません
RLMResults *dogs = [[Dog objectsInRealm:realm where:@"name contains 'Fido'"]];
この機能を使用すると、ディスクに保存されるデータが透過的にAES-256で必要に応じて暗号/複合化され、SHA-2 HMACによって検証されます。
暗号化したRealmファイルのインスタンスを作成するには同じ暗号化キーが必要になります。
詳しくは暗号化のサンプルコードをご覧ください。暗号化キーの作り方、キーチェーンへの安全な保存方法、Realmへのキーの渡し方などが理解できます。
暗号化したRealmを使う場合、わずかにパフォーマンスが下がり(10%未満)ます。
サードパーティのクラッシュレポートツール(CrashlyticsやPLCrashReporterなど)は、暗号化したRealmを初めて開く前に登録する必要があります。そうしないと、アプリが実際にクラッシュしていないにもかかわらず、誤ったクラッシュレポートを受け取る可能性があります。
キー値監視(KVO)
Realmオブジェクトのほとんどのプロパティはキー値監視(KVO)に対応しています。
すべてのRLMObjectのサブクラスにおける永続化対象(保存しないプロパティではない)のプロパティはKVOに対応しています。永続化される前のinvalidated
なRLMObjectとRLMArrayのプロパティも同様です。
永続化前(スタンドアローン)のRLMObjectのサブクラスのオブジェクトに対するキー値監視は、一般的なNSObject subclassと同様に動作します。
しかし、監視対象のオブジェクトは、監視対象として登録されている間は[realm addObject:obj]
などのメソッドでRealmに保存することができないので注意してください。
永続化済みのオブジェクトに対するキー値監視は少し異なる動作をします。
永続化済みのオブジェクトにおいては、そのプロパティの変わるタイミングが3種類あります。プロパティに値を直接代入したとき、[realm refresh]
メソッドを呼び出したとき、または別のスレッドがトランザクションをコミットして自動的にRealmが更新されたとき、そして別のスレッドから変更があったがその前に[realm beginWriteTransaction]
を呼び出してトランザクションを開始したために変更が通知されなかったときです。
直接代入する以外の2つのケースではすべての別スレッドから行われた変更は、一度に適用されます。そのため、KVOの通知は1回にまとめられます。途中の変更の状態は破棄されるので、1から10まで1つずつ数を増加させるプロパティがあった場合、10に変化する際の1つの通知だけを受け取ることになります。
トランザクションの外でプロパティの変更が行われる可能性があるので、-observeValueForKeyPath:ofObject:change:context:
メソッドの中で永続化済みのRealmオブジェクトを変更することは推奨されません。
NSMutableArray
のプロパティとは異なり、RLMArrayのプロパティに対する変更を監視するには-mutableArrayValueForKey:
を使う必要はありません(互換性のためにそのメソッドを利用しても同様に機能するようにはしていますが)。直接RLMArrayを変更するメソッドを呼び出すだけで(監視していれば)更新が通知されます。
サンプルコードに簡単なRealmとReactiveCocoa(Objective‑C)のサンプル、およびReactKit(Swift)のサンプルがありますので参考にしてください。
デバッグ
LLDBスクリプトとRealm Browserを使うと、Realmのデバッグをより簡単にできます。
最新のRealmのplugin/
に同梱されている、LLDB拡張rlm_lldb.py
は、デバッグ時にRLMObject、RLMResults、RLMArray オブジェクトを調べるのに非常に役に立ちます。LLDBの拡張を入れなければ、取得した結果のプロパティがnilや0と表示されます。
注意: LLDBスクリプトは、Objective‑Cにのみ対応しています。Swiftのサポートは開発中です。
暗号化されたRealmをデバッグする
暗号化されたRealmを使用しているプロセスにデバッガのセッションを接続することは現在サポートされていません。
REALM_DISABLE_ENCRYPTION=YES
環境変数を設定することでこの問題を避けることができます。コードを変更せずにデバッグを可能にするために、この環境変数は強制的に暗号化APIを無効にします。
当然ながら、セキュリティを保つためにすでに暗号化済みのRealmデータベースについては有効になりません(File::AccessError
の例外が発生します)。 新しくRealmファイルを作る場合に有効です。
テスト
デフォルトRealmの設定を変更する
Realmを使用する(実際のアプリケーションで使用する場合でも、テストで使用する場合でも)ときの最も簡単な方法は、デフォルトRealmを使用することです。
実際のアプリケーションのデータを上書きしてしまうことを防ぐため、あるいは各テストの状態が他のテストに影響することを防ぐために、それぞれのテストで新しいRealmファイルを使うようにデフォルトRealmを設定します。
// Realmを使う部分のテストを便利にするために、
// 専用のクラスを継承するようにします
@interface TestCaseBase : XCTestCase
@end
@implementation TestCaseBase
- (void)setUp {
[super setUp];
// テストケース名で区別して、テストごとにIn-memoryなRealmを使うようにします。
// こうすることで、テストによってアプリケーションのデータを変更してしまうことと、
// 他のテストに影響が及ぶことを防ぎます。
// そして、In-memoryなRealmを使うので
// 後始末として、データを削除する必要はありません。
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.inMemoryIdentifier = self.name;
[RLMRealmConfiguration setDefaultConfiguration:config];
}
@end
外部からRealmインスタンスを渡す
別の方法としては、すべてのRealmに関係するコードのメソッドを、RLMRealmインスタンスを受け取るようにします。アプリケーションの実行時とテスト実行時で、異なる Realm インスタンスを渡すようにします。
例えば、以下のテストコードは、JSON APIからユーザー情報を取得し、正しくデータが保存されているかをテストしています。
// Application Code
+ (void)updateUserFromServer {
NSURL *url = [NSURL URLWithString:@"http://myapi.example.com/user"];
[[[NSURLSession sharedSession] dataTaskWithURL:url
completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
[self createOrUpdateUserInRealm:[RLMRealm defaultRealm] withData:data];
}] resume];
}
+ (void)createOrUpdateUserInRealm:(RLMRealm *)realm withData:(NSData *)data {
id object = [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];
[realm transactionWithBlock:^{
[User createOrUpdateInRealm:realm withValue:object];
}];
}
// Test Code
- (void)testThatUserIsUpdatedFromServer {
RLMRealm *testRealm = [RLMRealm realmWithPath:kTestRealmPath];
NSData *jsonData = [@"{\"email\": \"help@realm.io\"}"
dataUsingEncoding:NSUTF8StringEncoding];
[ClassBeingTested createOrUpdateUserInRealm:testRealm withData:jsonData];
User *expectedUser = [User new];
expectedUser.email = @"help@realm.io";
XCTAssertEqualObjects([User allObjectsInRealm:testRealm][0],
expectedUser,
@"User was not properly updated from server.");
}
Realm.frameworkとテストターゲットをリンクしない
ダイナミックフレームワークとして Realm を使用している場合、ユニットテストのターゲットがRealmフレームワークを見つけられるようにしておく必要があります。 そのためには、ユニットテストの”Framework Search Paths”にRealm.framework
の親フォルダの場所を追加します。
テスト実行時に"Object type '...' not persisted in Realm"
というエラーが出る場合、Realm.framework
がテストターゲットにリンクされていることが原因です。 問題を解決するために、テストターゲットからRealmのリンクを解除してください。
また、モデルクラスのファイルはアプリケーションのターゲットか、フレームワークのターゲットのどちらか一方にのみリンクされている必要があります。 そうなってなければ、テスト時にモデルクラスに重複が生じて問題が発生する可能性があります。 詳しくはこちらのIssueをご覧下さい。
テストするコードは、全てテストターゲットからアクセス可能である必要があります。(Swift 1.2ではpublic
修飾子を使い、Swift 2.0では@testable
を使います。)
REST API
Realmは簡単にREST APIと組み合わせて使うことができます。そして、ローカルにデータを保存しない場合に比べて下記のような利点があります。
- データをRealmにキャッシュすることでオフラインでもアプリケーションが使用できるようになります。
- すべてのデータをデータをRealmキャッシュすると、ローカルでクエリを実行して、データを高速に検索できるのでユーザーエクスペリエンスが向上します。
- サーバーからデータをダウンロードする処理を、変更のあったものだけに減らすことができます。
ベストプラクティス
- 非同期通信 — 通信処理や時間のかかる処理は、UIを長時間ブロックしないように、バックグラウンドで行うべきです。同じように、Realmに大量のデータを追加/変更する場合も、バックグラウンドで行われるべきです。バックグラウンドで行われた変更に応答するには、Notifications 機能を使用します。
- 大量のデータをキャッシュする - 可能になったときに、事前にデータを取得してRealmに保存することを推奨します。そうすると、ローカルのデータに対して検索することができます。
- 追加と更新 - プライマリキーのようなユニークな識別子をデータセットが持っている場合、
+[RLMObject createOrUpdateInRealm:withValue:]
を使うことで、REST APIから受け取ったレスポンスで、より簡単にデータを追加/更新することができます。このメソッドは、更新が必要かどうか、すでに存在するデータかどうかなどを自動的にチェックしてくれます。
サンプルコード
下記は、単純なRealmとREST APIを連携させる例です。この例では、Foursquare APIからJSONデータを取得して、Default Realmにオブジェクトとして保存します。
似たような例として、この ビデオも参考になります。
まず始めに、デフォルト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"];
The response contains a JSON array of venues similar to this:
{
"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の対応するプロパティにJSONの値をマッピングするカスタムメソッドを作ることでも実現できます。
このサンプルの注目すべき点は、そのようにNSDicionaryを操作するのではなく、自動的にRLMObjectにJSONをマッピングしているところです。
この仕組みを正しく動作させるためには、RLMObjectのプロパティの構造と、JSONのキーを完全に一致させておく必要があります。
もし、それらが違っている場合、そのプロパティまたはキーは無視されます。以下の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)
レスポンスデータはJSONの配列として返ってくるので、配列をループして[Venue createInDefaultRealmWithObject:]
を使ってオブジェクトを作っています。
JSON データからVenue
オブジェクトと関連のオブジェクトをデフォルトRealmに追加しています。
//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 createOrUpdateInDefaultRealmWithValue:venue];
}
[realm commitWriteTransaction];
現バージョンにおける制限事項
現在のRealmはベータバージョンであり、バージョン1.0のリリースに向けて、継続的に機能追加および不具合の修正が行われています。
既知の問題についてのわかりやすい一覧はGitHub Issuesをご覧ください。
一般的な制限事項
Realmは、柔軟性とパフォーマンスのバランスをうまく保つため、保存するデータに対していくつかの制限事項があります。
- クラス名の長さは63バイト以下でなければなりません。UTF-8に含まれる文字をサポートしています。それ以上の長さのクラスがある場合、初期化の際に例外が投げられます。
- プロパティ名の長さは63バイト以下でなければなりません。UTF-8に含まれる文字をサポートしています。それ以上の長さのプロパティがある場合、初期化の際に例外が投げられます。
- NSData型のプロパティは、16MB以上のデータを保存することはできません。 それ以上のデータを保存するには、16MB以下の複数のデータにに分割するか、ファイルとして保存し、Realmにはファイルパスを記録します。 16MB以上のデータを保存しようとすると、実行時に例外が投げられます。
- NSDate型のプロパティは、保存したときにミリ秒以下が切り捨てられます。詳しくは、NSDateについてをご覧ください。
- 各Realmファイルのサイズは、アプリケーションごとにに割り当てられるメモリサイズを超えてはいけません。割り当てられるメモリサイズは、デバイスごとに異なり、実行時のメモリの断片化にも依存します。(詳しくは、rdar://17119975 をご覧ください)それ以上のデータを保存される場合は、Realmファイルを複数に分割してください。
きめ細やかな通知(Fine-grained notifications)は未サポートです
Realmのデータが変更されるたびに通知を受けることができますが、現在はどのような変更があったか(追加/更新/削除)を知ることができません。
近い将来、どのオブジェクトにどんな変更がなされたのかを含めて通知する機能を追加する予定です。
キー値監視(KVO)はオブジェクトごとの変更通知として利用することができます。特定のオブジェクトが削除されたことを検知することもできます。
NSDateはミリ秒以下が切り捨てられます
NSDate
は、いったんRealmに保存されるとミリ秒以下が切り捨てられます。 この問題は、現在修正中です。 詳しくは、 GitHub issue #875 をご覧ください。
それまで、ミリ秒以下の精度を持つ時間を保存したい場合は、NSTimeInterval
型のプロパティをお使いください。
モデルクラスのsetterおよびgetterメソッドはオーバーライドできません
Realmは、データベースのプロパティとデータベースの操作を連動させて、遅延ロードや高速な性能を実現するために、モデルクラスのsetterおよびgetterをオーバーライドしています。そのため、Realmモデルクラスではプロパティのsetterおよびgetterメソッドをオーバーライドすることはできません。
簡単な解決方法は保存しないプロパティとして宣言することです。保存しないプロパティのsetterおよびgetterメソッドは自由にオーバーライドすることが可能です。
ファイルサイズと中間データについて
SQLiteなどにデータを保存した時より、ディスクの使用容量が少なくなることを期待されることかと思います。 Realmファイルの容量が考えているよりも大きい場合はRLMRealm
は古い履歴データを残している可能性があります。
データの一貫性を保つために、Realmは最新のデータにアクセスしたときのみ履歴をアップデートします。 このことは、別のスレッドが多くのデータを長い時間をかけて書き込んでいる最中にデータを読み出そうとした場合、履歴はアップデートされずに古いデータを読み出すことになります。結果として、履歴の中間データが増加していくことになります。
この余分な領域は、最終的には再利用されるか消去されます。(強制的に空き領域を消去するには、writeCopyToPath:error:
を使ってファイルをコピーします。そのとき、自動的にファイルサイズが最適化されます。)
この問題を避けるには、invalidate
メソッドを呼び出し、Realmにこれまでに取得したデータはもう必要なくなったことを知らせてください。 そうすると、Realmは中間データの履歴を解放します。そして、次のアクセスのときに最新のデータを使うようにRealmが更新されます。
また、GCDを使ってRealmにアクセスしたときにも、この問題が発生する可能性があります。ブロックの実行が終了した後も、オートリリースプールのオブジェクトが解放されずに、RLMRealm
が解放されるまで古い履歴のデータが残ることによります。
この問題を避けるためにGCDのブロック内でRealmにアクセスするときは、明示的にオートリリースプールを利用してください。
FAQ
Realmのライブラリは、どのくらいの容量がありますか?
アプリケーションをリリース用にビルドすると、Realmライブラリによってアプリケーション自体の容量は1MB程度増加します。
現在、配布されているRealmのライブラリは、iOSとwatchOS両方のシミュレータ用のバイナリやデバッグシンボル、そしてBitcodeを含むため著しく大きくなっています。それらはビルド時にXcodeによって自動的に取り除かれます。
Realmをプロダクション環境で使うことはできますか?
Realmは、2012年から商用のプロダクトで利用されています。
現在のRealmを利用する場合は、RealmのObjective‑C、およびSwiftのAPIが、コミュニティのフィードバックを受けて変わりうるものだとしてお使いください。 機能追加、不具合の修正も同様に考えてください。
Realmを使うために料金を支払う必要がありますか?
いいえ、Realmは完全に無料です。商用の製品で利用することも可能です。
Realmはどのようなビジネスプランなのですか?
すでにエンタープライズ向けの商品の販売や、周辺サービスによって収益を得ています。もし現在リリースされているrealm-cocoa以上の機能が必要であれば、いつでもメールで、お気軽にご連絡ください。
また私たちのビジネスとは関係なく、realm-cocoaはオープンソースのまま開発を続け、Apache License 2.0として公開し続けます。
“tightdb”や”core”という文字をコードの中で見たのですが、これは何ですか?
TightDBというのは、C++で実装されたストレージエンジンの古い名前です。現在、コアのソースコードはオープンソースではありませんが、近い将来Apache License 2.0で公開する予定です。 バイナリについては、Realm Core (TightDB) Binary Licenseで利用可能です。