古いバージョンのドキュメントを表示しています。
最新版のドキュメントはこちらからご覧になれます。
はじめに
インストール
- ここから 最新のRealm をダウンロードしてください。
- この中にある
ios/
またはosx/
フォルダからRealm.framework
をXcodeのナビゲータエリアにドラッグ&ドロップしてください。この時、 Copy items if needed にチェックが入ってることを確認してから Finish をクリックしてください。 - ナビゲータエリアでプロジェクトをクリックし、アプリのTargetを選択します。 Build Phases タブの Link Binary with Libraries から、 libc++.dylib を追加してください。
- Swiftの場合は、 さらに
Swift/RLMSupport.swift
も同様にドラッグ&ドロップし、 Copy items if needed にチェックが入った状態で、ナビゲータエリアに追加してください。 - OSXプロジェクトの場合は、Build Phase タブのパネルの左上にある
+
ボタンをクリックし、New Copy Files Phase
を選択して新しくビルドフェーズを追加してください。 そして名前をCopy Frameworks
に変更し、Destination
をFrameworks
に変え、Realm.framework
を追加してください。
- Podfileに
pod "Realm"
を追加してください。 - コマンドラインで
pod install
を実行してください。 - CocoaPodsによって作成された
.xcworkspace
をXcodeで開いてください。 - Swiftの場合は以下もご覧ください
- ここから 最新のRealm をダウンロードしてください。
Swift/RLMSupport.swift
をXcodeのナビゲータエリアにドラッグ&ドロップしてください。この時、 Copy items if needed にチェックが入ってることを確認してから Finish をクリックしてください。
詳しくは、CocoaPods website をご覧ください。
- ここから 最新のRealm をダウンロードしてください。
ios/
フォルダからRealm-dynamic.framework
とRealm-dynamic-simulator.framework
をXcodeのナビゲータエリアにドラッグ&ドロップしてください。この時、 Copy items if needed にチェックが入ってることを確認してから Finish をクリックしてください。- Build Phase タブのパネルの左上にある
+
ボタンをクリックし、New Copy Files Phase
を選択して新しくビルドフェーズを追加してください。 そして名前をCopy Frameworks
に変更し、Destination
をFrameworks
に変え、Realm-dynamic.framework
を追加してください。
注意: Dynamic Frameworkは、iOS8でのみ使用可能です。
github "realm/realm-cocoa"
をCartfileに追加してください。carthage update
を実行してください。- Swiftの場合は以下もご覧ください
- ここから 最新のRealm をダウンロードしてください。
Swift/RLMSupport.swift
をXcodeのナビゲータエリアにドラッグ&ドロップしてください。この時、 Copy items if needed にチェックが入ってることを確認してから Finish をクリックしてください。
詳しくは、Carthage project をご覧ください。
Realm Browser
RealmBrowser は、Realmの中で使われるている .realm
ファイルを閲覧、編集するMacアプリです。最新のRealm の browser/
フォルダの中に入っています。 また、Tools > Generate demo database を選択することでサンプルデータを含んだ、テスト用のRealmデータベースを作ることもできます。
Xcode Plugin
RealmPluginは、現在、一時的に使用できません。修正中ですので、しばらくお待ち下さい。
API Reference
Realmで使用できるClass, Methodに関しては、API Reference をご覧ください。
サンプル
最新のRealm のexamples/
ファルダにObjective‑C, Swift, RubyMotionそれぞれのiOS/Macのサンプルコードがあります。RealmのMigration, UITableViewControllerとの使い方、Encryption, コマンドラインツールなど、様々な機能が紹介されていますので是非、ご参考にしてください。
ヘルプ
- community newsletter に参加することで定期的にRealmに関するのTipsやUseCase, ブログポストやチュートリアルなど、Realmの最新情報がGetできます。
- StackOverflow: 以前の質問はStackOverflowで #realm をご覧ください。
- Twitter: お気軽に @realm かハッシュタグ #realm でツイートしてください。
- Email: realm-cocoa@googlegroups.com
モデル
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())
}
Realmは、開発者が定義するRealmモデルクラスをアプリ起動時に、パースし読み込んでいます。Realmモデルクラスは、正しく定義してください。
詳しくは RLMObject をご覧ください。
プロパティの型
Realmでは、以下のプロパティが使えます。 BOOL
, bool
, int
, NSInteger
, long
, long 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モデルクラスのカスタマイズ
クラスメソッドをオーバーライドすることで、さらに機能を追加することもできます:
+attributesForProperty:
:インデックスを作成するなど、プロパティの属性が設定できます。+defaultPropertyValues
: プロパティのデフォルト値を設定することができます。+primaryKey
: PrimaryKeyが指定できます。
@interface Person : RLMObject
@property NSInteger id;
@property NSString *name;
@end
@implementation Person
+ (NSString *)primaryKey {
return @"id";
}
@end
class Person: RLMObject {
dynamic var id = 0
dynamic var name = ""
override class func primaryKey() -> String {
return "id"
}
}
+ignoredProperties
: 指定されたプロパティは、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トランザクションを使うことをオススメします。
詳しくは、RLMRealm と RLMObject をご覧ください。
クエリ
クエリを実行するとRLMObjectオブジェクトを含んだ、RLMResultsオブジェクトが結果として返ってきます。 RLMResultは、NSArrayと似たようなインターフェイスを持ち、添え字アクセスでオブジェクトにアクセスすることもできます。 NSArrayと違う点は、RLMResultは、RLMObjectのみを含むことができるという点です。 詳しくは、RLMResults をご覧ください。
Realm上での、プロパティアクセスを含む、全てのクエリは遅延評価されてます。プロパティにアクセスした時に、初めてデータが読み込まれます。
クエリを実行したときに返ってくる結果は、データのコピーではありません。トランザクションを使ってそのデータを変更した場合、ディスクのデータを変更したことになります。 また、RLMResultに含まれるRLMObjectから、 関連 のあるオブジェクトをフェッチすることもできます。
オブジェクトの取得
最も、基本的なオブジェクトのフェッチ方法は、[RLMObject allObjects]
です。 これは、指定したクラスのインスタンスをdefaultRealm内から全てフェッチしてきます。
// 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, long 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
で、 name
が B
からはじまる 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'")
Realms
デフォルトRealm
すでにお気付きだと思いますが、これまで [RLMRealm defaultRealm]
を呼ぶことで、変数 realm
を初期化してきました。 このメソッドは、アプリのDocumentsフォルダにある、“default.realm” ファイルのRLMRealmオブジェクトを返します。
+[RLMRealm setDefaultRealmPath:]
を使うことで、デフォルトRealmへのパスを変更できます。この機能は、特にテスト時やiOS8で利用可能の Shared Container で役立ちます。
RealmのAPIにあるたくさんのメソッドで、Realmインスンタスを引数としてとるものと、自動でデフォルトRealmが使われる両方があります。例えば、[RLMObject allObjects]
は、[RLMObject allObjectsInRealm:[RLMRealm defaultRealm]]
と同じ意味になります。
その他のRealm
場合によっては、複数のRealmが必要な時があります。 詳しくは、 [RLMRealm realmWithPath:]
, [RLMRealm realmWithPath:readOnly:error:]
をご覧ください。
[RLMRealm realmWithPath:]
で引数として渡すパスは、書き込み可能である必要があります。 Realmファイルの基本的な保存場所は、iOSでは、”Documents” フォルダ、OSXでは、”Application Support” フォルダです。 Appleの iOS Data Storage Guidelines に沿って設計してください。
In-Memory Realms
基本的には、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インスタンスへの強参照を常に保持しておく必要があります。
スレッド間での実行
複数のスレッドで、同じRealmを使う場合は、各スレッドで[RLMRealm defaultRealm]
, [RLMRealm realmWithPath:]
または [RLMRealm realmWithPath:readOnly:error:]
でインスタンスをそれぞれ生成する必要があります。 Realmファイルへのパスさえ同じであれば、RLMRealmオブジェクトはディスク上で同じものを指します。
複数のスレッド間での、RLMRealmインスタンスの共有はサポートされていません。 同じRealmファイルを指すRLMRealmのインスタンスのrealdOnly属性は、統一しないといけません。(全てreadwriteにするか全てreadonlyにするかしないといけません)
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()
}
}
Realm間でのオブジェクトのコピー
Realmのオブジェクトを他のRealmへコピーするには、+[RLMObject createInRealm:withObject:]
へ元のオブジェクトを引数として渡すことでできます。 例えば、 [MyRLMObjectSubclass createInRealm:otherRealm withObject:originalObjectInstance]
のように使います。
初期データとしてのRealm
アプリをインストールした時からすぐに使えるような初期データをアプリに入れておくことは、とても一般的な方法です。以下が、Realmでのやり方です。
- はじめに、初期データの入ったRealmを用意します。最終的にリリースする時と同じデータモデルを定義し、データを入れます。Realmファイルは、クロスプラットフォームですので、OSXアプリや、シュミレータで動くiOSアプリでも問題ありません。(JSONImportをご覧ください)
- 初期データを入れるコードの終わりに、ファイルの圧縮コピーをするメソッドを使用し、Realmファイルを圧縮してください。(
-[RLMRealm writeCopyToPath:error:]
をご覧ください)このメソッドを使ってRealmファイルをコピーすると、Realmのファイルサイズを軽減でき、ユーザーがアプリをダウンロードするときに軽くなります。 - 新しく圧縮したRealmファイルを、アプリのXcodeのプロジェクトナビゲーターにDrag&Dropします。
- XcodeのBuild Phaseタブで、”Copy Bundle Resources” に圧縮したRealmファイルを追加します。
- アプリから、追加したRealmファイルがアクセスできるようになります。
[[NSBundle mainBundle] pathForResource:ofType:]
を使って、パスを見つけることができます。 - これで、
[RLMRealm realmWithPath:readOnly:error:]
でreadOnlyなRealmを作ることもできるし、その初期データを含んだRealmに書き込みたい場合は、[[NSFileManager defaultManager]
を使い、アプリのDocuments
ディレクトリにRealmファイルをコピーしてから、[RLMRealm realmWithPath:]
にパスを渡し、Realmインスタンスを作るようにしてください。
Realmファイルを使っての初期データの組み込みは、migration sample appをご参照ください。
リレーションシップ
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)
Inverse Relationships
逆方向のリレーションシップを張っておくことで、特定のプロパティを用いて、関連するオブジェクトへアクセスすることができます。 -linkingObjectsOfClass:forProperty:
を使い、Dog
クラス定義しておくことで、紐づくオブジェクトを取得できるようになります。 以下のようにreadonlyのowners
プロパティをDog
クラスに定義することで、逆のリレーションシップを張ることができます。
@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
class Dog: RLMObject {
dynamic var name = ""
dynamic var age = 0
var owners: [Person] {
// Realm doesn't persist this property because it only has a getter defined
// Define "owners" as the inverse relationship to Person.dogs
return linkingObjectsOfClass("Person", forProperty: "dogs") as [Person]
}
}
通知
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でのデータモデルは、シンプルな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
}
ここで、firstName
と lastName
を一つにして、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)"
}
}
})
一度、マイグレーション処理が適応されると、その後はいつもと同じようにRealm上の全てのオブジェクトに対してアクセスできます。
バージョンの追加方法
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 をご覧ください。
Linear Migrations
二人のユーザーがいると考えてください。 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を代入することをオススメします。
暗号化
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でRealmファイルを保護すると、他のプラットフォームに移動させることができなくなります。(NSFileProtectionは、iOSでのみ有効な機能です) 2) パスワードロックがかかるだけで、Realmファイルは暗号化されません。 これらの制限を避けるために(もしくは、OS Xアプリでお使いの場合)、Realmが実装している暗号化方法を使用することをオススメいたします。
Realmでは、64byte暗号化キーを用いて、AES-256とSHA-2暗号化方式でデータベースファイルを暗号化する機能があります。
// Generate a random encryption key
NSMutableData *key = [NSMutableData dataWithLength:64];
SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes);
// Open the encrypted Realm file
NSError *error;
RLMRealm *realm = [RLMRealm realmWithPath:RLMRealm.defaultRealmPath
key:key
readOnly:NO
error:&error];
if (!realm) {
// If the encryption key is wrong, `error` will say that it's an invalid database
NSLog(@"Error opening realm: %@", error);
return;
}
// Use the Realm as normal
RLMResults *dogs = [[Dog objectsInRealm:realm where:@"name contains 'Fido'"]];
// Generate a random encryption key
let key = NSMutableData(length: 64)!
SecRandomCopyBytes(kSecRandomDefault, UInt(key.length),
UnsafeMutablePointer<UInt8>(key.mutableBytes))
// Open the encrypted Realm file
var error: NSError?
let realm = RLMRealm(path: RLMRealm.defaultRealmPath(),
encryptionKey: key, readOnly: false, error: &error)
if realm == nil {
// If the encryption key is wrong, `error` will say that it's an invalid database
println("Error opening realm: \(error)")
return
}
// Use the Realm as normal
let dogs = Dog.objectsInRealm(realm, "name contains 'Fido'")
この機能を使用すると、ディスクに保存されるデータがAES-256で暗号化され、必要に応じ、複合化されます。 また、その後、RLMRealmインスタンス作成時には、同じ暗号化キーが必要になります。インスンタス作成後は、メモリ上に暗号化キーを保持され、指定したパス先にあるRealmファイルへアクセスは、自動的に暗号化キーが使用されるようになります。 たとえば、convenienceメソッドを使って、defaultRealmを指定するコードは以下のようになります。
// Generate a random encryption key
NSMutableData *key = [NSMutableData dataWithLength:64];
SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes);
// Set the encryption key for the default Realm
[RLMRealm setEncryptionKey:key forRealmsAtPath:RLMRealm.defaultRealmPath];
// Use the Realm as normal
RLMResults *dogs = [[Dog objectsWhere:@"name contains 'Fido'"]];
// Generate a random encryption key
let key = NSMutableData(length: 64)!
SecRandomCopyBytes(kSecRandomDefault, UInt(key.length),
UnsafeMutablePointer<UInt8>(key.mutableBytes))
// Set the encryption key for the default Realm
RLMRealm.setEncryptionKey(key, forRealmsAtPath: RLMRealm.defaultRealmPath())
// Use the Realm as normal
let dogs = Dog.objectsWhere("name contains 'Fido'")
詳しくは、Objective‑CとSwiftの暗号化のサンプルコードをご覧ください。暗号化キーの作り方、キーチェーンへの安全な保存方法、Realmへのキーの渡し方などが分かるかと思います。 注意することは、暗号化機能を使うと、デバッガがきちんと機能しません。暗号化は、開発中は使用せず、リリースビルドでのみ有効にすることをオススメいたします。
暗号化したRealmを使う場合、通常よりも20%未満パフォーマンスが下がります。
デバッグ
RealmのLLDB拡張とRealm Browserを使うと、Realmのデバッグをより簡単にしてくれます。
最新のRealmの plugin/
に同梱されている、LLDB拡張 rlm_lldb.py
は、デバッグ時にRLMObject, RLMResults, RLMArrayオブジェクトを調べるのに非常に役に立ちます。LLDBの拡張を入れなければ、フェッチ結果のプロパティが nil や 0 と表示されます。
注意: LLDBスクリプトは、Objective‑Cでのみ対応済みです。Swiftのサポートは開発中です。
テスト
Realmを使ったアプリのテスト
最も、簡単な方法は、defaultRealm を使用することです。テストコードを実行する前に、 +[RLMRealm setDefaultRealmPath:]
でテスト用のdefaultRealmのパスを設定することで、アプリで使用されているデータを上書きされるのを避けられます。
また、テストする部分としては、引数として、RLMRealmのインスタンスが渡されるメソッドなどです。実際にアプリ実行時とテスト実行時で、異なるRealmインスタンスを投げても問題ないかテストします。 例えば、以下のテストコードは、JSON APIからGETを使ってユーザー情報を取得し、正常にデータが作成されているかをテストしています。
// 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 withObject: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.");
}
// Application Code
class func updateUserFromServer() {
let url = NSURL(string: "http://myapi.example.com/user")
NSURLSession.sharedSession().dataTaskWithURL(url) { data, _, _ in
self.createOrUpdateUserInRealm(RLMRealm.defaultRealm(), withData: data)
}
}
public class func createOrUpdateUserInRealm(realm: RLMRealm, withData data: NSData) {
let object: [String: String] =
NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
realm.transactionWithBlock {
User.createOrUpdateInRealm(realm, withObject: object)
}
}
// Test Code
func testThatUserIsUpdatedFromServer() {
let testRealm = RLMRealm(path: kTestRealmPath)
let jsonString: NSString = "{\"email\": \"help@realm.io\"}"
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)
ClassBeingTested.createOrUpdateUserInRealm(testRealm, withData: jsonData)
let expectedUser = User()
expectedUser.email = "help@realm.io"
XCTAssertEqual(User.allObjectsInRealm(testRealm).first as User,
expectedUser,
"User was not properly updated from server.")
}
テスト実行時に "Object type '...' not persisted in Realm"
というエラーが出る場合、Realm.framework がテストTargetにリンクされていないことが原因です。
CocoaPodsをお使いの場合は、以下のようにPodfileをお書きください。
target 'MyApp'
pod 'Realm'
end
target 'MyAppTests', exclusive: true do
pod 'Realm/Headers'
end
Realmのテスト
Realm自体は、全てのコミットにおいて広い範囲のテストが行われています。 Realmはオープンソースで、私たちは、あなたのコントリビュートを喜んで歓迎いたします。
REST API
Realmでは、簡単に REST API と統合ができ、いくつかの利点があります。
- REST API のみだと常にネットとの接続が必要ですが、データをキャッシュすることでオフラインでも、アプリを動作させることができます。
- 完全にデータをキャッシュさせておくことで、
REST API
だけでは、不可能なローカルでクエリを実行したり、データを閲覧したりできます。 - データを保存させておくことで、サーバーサイド側での処理を新しい変更だけをロードするものだけに減らすことができます。
ベストプラクティス
- 非同期リクエスト — リクエストを投げる時や、他のブロッキング操作は、UIスレッドを止めてしまう恐れがあるので、バックグラウンド処理で行うことが推奨されています。これと同じ理由で、Realmでの膨大なデータの変更は、バックグラウンド処理で行うべきです。バックグラウンドで行われた変更に対応するには、Notifications 機能を使いましょう。
- 膨大なデータのキャッシュ - 可能な時に事前にデータをRealmにフェッチし保存させておくことをオススメします。そのようにしておくと、いつでもローカルで保存してあるデータに対してクエリが投げられます。
- 挿入と更新 - 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をより深く理解する、次のステップとして、サンプルコードを用意しています。 HAPPY HACKING!! Google グループ Realm で、あなたはいつでもRealmデベロッパーと議論ができます。
開発中の機能
Realmは、現在β版としてリリースされています。バージョン1.0に向けて、機能追加、バグの修正などを行っています。私たちは、次に導入予定のリストを作成しました。 詳しくは、GitHub issues をご覧ください。
一般的な制約
Realmは、柔軟性とパフォーマンスのバランスを上手く保つため、保存するデータに対していくつか制約があります。
- クラス名は、UTF-8の0~63バイトである必要があります。これを超過している場合、ロード時に例外が投げられます。
- プロパティ名は、UTF-8の0~63バイトである必要があります。これを超過している場合、ロード時に例外が投げられます。
- NSData型のプロパティでは、16MB以上のデータを持つことはできません。 これ以上のデータを保存しようとすると、16MBのチャンクに分割されるか、ファイルにそのまま書き込まれるかのどちらかです。プロパティの値が16MB以上のデータを保存しようとすると、それを実行時に例外が投げられます。
- NSDate型のプロパティは、保存されると少数点以下が切り捨てられます。
+[NSDate distantFuture]
や+[NSDate distantPast]
で作られたデータは保持できません。詳しくは、NSDateについてをご覧ください。 - Realmファイルのサイズは、一つのiOSアプリに割り当てられるメモリサイズを超えてはいけません。割り当てられるメモリサイズは、デバイス毎に違い、また、どのくらいメモリのフラグメンテーションが進んでるかにも依存します。(ここについては、Open Radarのrdar://17119975をご覧ください) これ以上のデータを保存される場合は、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ファイルを作成してください。 マルチプロセスサポートは、近々、追加するする予定です。
ファイルサイズと中間バージョンについて
誰もが、SQLiteを使ってデータを保存した時よりもディスクの使用容量が少なくなることを期待されることかと思います。 Realmファイルの容量が非常に大きくなった場合、Realmがオブジェクトをトラッキングするために中間データを作っているためです。 invalidate
を呼ぶことで、Realmオブジェクトを解放してください。 データの一貫性を保つために、データをフェッチしたときのみバージョンがアップデートされます。 このことは、Realmからデータを読み込み、別のスレッドでそのデータの更新が行われた場合、読み込まれたデータの情報はアップデートされないということです。 そして、Realmは、この中間バージョンを管理するために、内部でのみ使用されるデータが増えていき、その結果、ディスクの使用領域が肥大化してしまうことになります。 (この余分なスペースは、今後の書き込みで再度、使われるか、圧縮されることになります。例えば、writeCopyToPath:error
が呼ばれた時などです)
その代わりに、invalidate
を呼ぶことで、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を使うのにお金を払わないといけませんか?
いいえ、Realmは、完全に無料です。商業利用も可能です。
どのようなビジネスプランなのですか?
すでにエンタープライズ向けの商品の販売や、周辺サービスによって収益を得ています。もし現在リリースされているものやrealm-cocoaで更に必要なものがあれば、いつでもメールで、お気軽にご連絡ください。また私たちのビジネスとは関係なく、realm-cocoaはオープンに開発をつづけていき、Apache License 2.0の元にオープンソースで公開し続けます。
“tightdb” や “core” という文字をコードの中で見たのですが、これは何ですか?
TightDBというのは、C++で実装されたストレージエンジンの名前です。現在、オープンソースではありませんが、Apache License 2.0として公開することを検討中です。 バイナリリリースは、Realm Core (TightDB) Binary License として利用可能です。
</div>