古いバージョンのドキュメントを表示しています。
最新版のドキュメントはこちらからご覧になれます。
Objective‑C や Objective‑C と Swift のアプリから Realm を使われる場合は、Realm Objective‑C を使用することをお考えください。
はじめに
Download Realm または、ソースコードは realm-cocoa on GitHub
必要条件
- iOS 8.0 または Mac OSX 10.9以上
- Xcode 6.3以上
インストール
- ここから 最新のRealm をダウンロードしてください。
- この中にある
ios/
またはosx/
フォルダからRealmSwift.framework
をXcodeの “General” タブの “Embedded Frameworks” にドラッグ&ドロップしてください。この時、 Copy items if needed にチェックが入ってることを確認してから Finish をクリックしてください。 - メインターゲットの “Build Settings” タブの “Framework Search Paths” で
RealmSwift.framework
のパスを追加してください。 - ユニットテストのターゲットの “Build Settings” タブの “Framework Search Paths” で
RealmSwift.framework
のパスを追加してください。 - iOS8 のプロジェクトで Realm を使われている場合は、”Build Phases” に “Run Script Phase” を作成し、以下のスニペットをコピーしてください。
${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/Resources/strip-frameworks.sh
この手順は、ユニバーサルバイナリをアーカイブする時に AppStore のサブミット時のバグを回避するために必要となります。 注意: Dynamic Framework は、OSX と iOS8 以上でのみ使用可能です。
- CococaPods 0.37.1 またはそれ以降のバージョンをインストールしてください(
[sudo] gem install cocoapods
) - Podfile の中に
use_frameworks!
と アプリのターゲットにpod 'Realm'
を追加してください。 - コマンドラインで
pod install
を実行してください。 - CocoaPods によって作られた
.xcworkspace
ファイルを開いてください。 詳しくは CocoaPods の Webサイト をご覧ください。
- Carthage 0.7.2 またはそれ以降のバージョンをインストールください。
github "realm/realm-cocoa"
をCartfileに追加してください。carthage update
を実行してください。- iOS:
RealmSwift.framework
をCarthage/Build/iOS/
ディレクトリから Xcode の “General” タブにある “Linked Frameworks and Libraries” にドラッグしてください。 ** OS X:**RealmSwift.framework
をCarthage/Build/Mac/
ディレクトリから Xcode の “General” タブにある “Embedded Binaries” にドラッグしてください。 -
iOS アプリのターゲットの “Build Phases” の “+” ボタンをクリックし、以下のような “New Run Script Phase” を追加します。
/usr/local/bin/carthage copy-frameworks
この時 “Input Files” にはフレームワークのパスを指定します。
$(SRCROOT)/Carthage/Build/iOS/RealmSwift.framework
このスクリプトは、ユニバーサルバイナリをアーカイブする時に AppStore のサブミット時のバグを回避するために必要となります。
詳しくは、Carthage のプロジェクトページ をご覧ください。
Realm Browser
RealmBrowser は、Realm の中で使われるている .realm
ファイルを閲覧、編集する Mac アプリです。最新のRealm の browser/
フォルダの中に入っています。 また、Tools > Generate demo database を選択することでサンプルデータを含んだ、テスト用の Realm データベースを作ることもできます。
Realm ファイルの特定の仕方が分からない方は、この StackOverflow の回答をご覧ください。
Xcode Plugin
Xcode プラグインを使うことで Realm モデルファイルの作成が簡単になります。
この Xcode プラグインをインストールする一番簡単な方法は、Alcatraz で “RealmPlugin” と検索することです。
また、手動でもインストールする場合は、release zip の plugin/RealmPlugin.xcodeproj
をビルドすることでインストールができます。
プラグインをインストール後に Xcode を再起動すると、Xcode で新しくファイルを作成時(File > New > File… または ⌘N)に Realm Model が追加されています。
API Reference
Realm で使用できる Class、Method に関しては API Reference をご覧ください。
サンプル
最新の Realm の examples/
ファルダに iOS/Mac のそれぞれのサンプルコードがあります。Realm の Migration(マイグレーション)、 UITableViewController との使い方、Encryption(暗号化)、コマンドラインツールなど様々な機能が紹介されていますので是非、ご参考にしてください。
ヘルプ
- 直接質問をしたい場合は、こちらの Slack グループでお気軽にご質問ください(Slack グループは日本語です)
- バグ報告や機能リクエストについては GitHub レポジトリにご投稿ください。
- Discussions & Support: realm-cocoa@googlegroups.com.
- StackOverflow: 以前の質問はStackOverflowで #realm をご覧ください。
- Community Newsletter に参加することで定期的にRealmに関するの Tips や UseCase、ブログポストやチュートリアルなど、Realm の最新情報が届きます。
モデル
Realm で使うモデルクラスは、一般的な のクラスで定義できます。 Object
クラスまたは、自分で作った Realm モデルクラスのサブクラスを作ることで簡単に作成できます。 また、他の クラスのように、メソッドやプロトコルを追加することもできます。 注意することは、Realm モデルクラスのインスタンスは、他のスレッドへの受け渡しができません。また、Realm モデルクラスのプロパティのインスタンス変数に直接、アクセスすることはできません。
リレーションシップやネストしたデータ構造も、オブジェクトの型のプロパティを持たせることや List
を使って作れます。
import RealmSwift
// Dog model
class Dog: Object {
dynamic var name = ""
dynamic var owner: Person? // Can be optional
}
// Person model
class Person: Object {
dynamic var name = ""
dynamic var birthdate = NSDate(timeIntervalSince1970: 1)
let dogs = List<Dog>()
}
Realm は、開発者が定義した Realm モデルクラスをアプリ起動時に、パースし読み込んでいます。たとえ一度も使わない場合であっても Realm モデルクラスは、正しく定義するようにしてください。
詳しくは Object をご覧ください。
プロパティの型
Realmでは、以下のプロパティが使えます。 Bool
, Int8
, Int16
, Int32
, Int64
, Double
, Float
, String
, NSDate
(少数点以下は切り捨て), NSData
一対一や一対多のようなリレーションシップのために、List<Object>
, Object
のプロパティも定義できます。RLMObject
のサブクラスも使えます。
プロパティ属性
Realm モデルのプロパティはデータベースのデータのアクセッサになるため var dynamic
属性である必要があります。 一つだけ例外があり List
プロパティは dynamic
として宣言できません。Generics のプロパティは Objective‑C ランタイムでは表現できないためダイナミックディスパッチとなる dynamic
は使用できません。
Realmモデルクラスのカスタマイズ
クラスメソッドをオーバーライドすることで、さらに機能を追加することもできます:
Object.indexedProperties()
: 指定したプロパティにインデックスを作成できます。class Book: Object { dynamic var price = 0 dynamic var title = "" override static func indexedProperties() -> [String] { return ["title"] } }
class Book: Object {
dynamic var price = 0
dynamic var title = ""
}
Object.primaryKey()
: プライマリキーが指定できます。プライマリーを指定することで、効率的に取得、アップデートでき、それぞれのオブジェクトをユニークに保てます。class Person: Object { dynamic var id = 0 dynamic var name = "" override static func primaryKey() -> String? { return "id" } }
Object.ignoredProperties()
: 指定されたプロパティは、Realm に保存されず無視されます。class Person: Object { dynamic var tmpID = 0 var name: String { // computed properties are automatically ignored return "\(firstName) \(lastName)" } dynamic var firstName = "" dynamic var lastName = "" override static func ignoredProperties() -> [String] { return ["tmpID"] } }
書き込み
Realm へのオブジェクトの追加、変更、削除は、必ずトランザクションを使って行ってください。
Realm モデルクラスは、インスタンス化し、他の オブジェクトと同じように使うことができます。スレッド間やアプリの再起動時に Realm オブジェクトのデータを共有するときは、Realm にデータを一度保存しなければいけません。これらの操作は、トランザクションの中で行ってください。
オブジェクトの追加
以下のようにオブジェクトを追加します。
// Create a Person object
let author = Person()
author.name = "David Foster Wallace"
// Get the default Realm
let realm = Realm()
// You only need to do this once (per thread)
// Add to the Realm inside a transaction
realm.write {
realm.add(author)
}
オブジェクトを Realm に追加した、その後はずっと使用することができ、全ての変更が記録されていきます。(ただし、変更時は、トランザクションを使わなければいけません) もし、同じ Realm を複数のスレッドで共有している場合、トランザクションがコミットされた時点で、別のスレッドにもその変更が適用されます。
書き込み処理が行われている間は、他の処理をブロックしていることになります。これは、他の Persistence システムでも同じことが起こり、一般的なベストプラクティスを使うことをオススメします。 バックグランド処理 をご覧ください。
Realm は MVCC アーキテクチャーであるため、Write トランザクションが開始されている状態でも、読み込み処理は正しくできます。同時に複数のスレッドから書き込みする場合でない限り、長めの Write トランザクションを使うことをオススメします。
オブジェクトの更新
オブジェクトの更新は、Write トランザクションの中で行ってください
// Update an object with a transaction
realm.write {
author.name = "Thomas Pynchon"
}
Realm モデルクラスで primary key
を定義してる場合にオブジェクトを更新する場合や、オブジェクトを追加する場合は、Realm().add(_:update:)
を使ってください。
// Creating a book with the same primary key as a previously saved book
let cheeseBook = Book()
cheeseBook.title = "Cheese recipes"
cheeseBook.price = 9000
cheeseBook.id = 1
// Updating book with id = 1
realm.write {
realm.add(cheeseBook, update: true)
}
ここで id = 1
の Book オブジェクトが Realm に保存されていない場合は、新しい Book オブジェクトを追加します。
オブジェクトの削除
オブジェクトを削除するときには、Write トランザクションの中で、Realm().delete(_:)
に削除したいオブジェクトを引数に呼び出してください。
let cheeseBook = ... // Book stored in Realm
// Delete an object with a transaction
realm.write {
realm.delete(cheeseBook)
}
Realm に保存されてるオブジェクトを全て削除することもできます。注意することとして、Realm ファイルでは、ディスクスペースを効率的に再利用するために、サイズを維持したままにします。
// Delete all objects from the realm
realm.write {
realm.deleteAll()
}
クエリ
クエリを実行すると Object オブジェクトを含んだ、Results
オブジェクトが結果として返ってきます。 Results は、Array と似たようなインターフェイスを持ち、添え字アクセスでオブジェクトにアクセスすることもできます。 NSArray と違う点は、Results は、Object のみを含むことができるという点です。
Realm 上での、プロパティアクセスを含む、全てのクエリは遅延評価されてます。プロパティにアクセスした時に、初めてデータが読み込まれます。
クエリを実行したときに返ってくる結果は、データのコピーではありません。トランザクションを使ってそのデータを変更した場合、ディスクのデータを変更したことになります。 また、Result に含まれる Object から、関連 のあるオブジェクトをフェッチすることもできます。
オブジェクトの取得
最も、基本的なオブジェクトのフェッチ方法は Realm().objects(_:)
です。 これは、指定したクラスのインスタンスを defaultRealm 内から全てフェッチしてきます。
// Query a Realm
let dogs = Realm(path: "pets.realm").objects(Dog)
Predicate を使ったフェッチ
普段から NSPredicate の扱いに慣れているなら、Realm でのオブジェクトの取得仕方を知っているも同然です。Object, Realm, List, Result は全て NSPredicate インスタンスまたは、Predicate の構文を使ってのオブジェクトのフェッチをサポートしています。
例えば、以下のように Results().filter(_:...)
は、Dog クラスの color = tan
で、name の値が B
からはじまる Dog クラスのインスタンスを defaultRealm 内から全てフェッチしてきます。
let realm = Realm()
// Query using a predicate string
var tanDogs = realm.objects(Dog).filter("color = 'tan' AND name BEGINSWITH 'B'")
// Query using an NSPredicate
let predicate = NSPredicate(format: "color = %@ AND name BEGINSWITH %@", "tan", "B")
tanDogs = realm.objects(Dog).filter(predicate)
詳しくは、Apple’s Predicates Programming Guide をご覧ください。Realm では、たくさん一般的な Predicate 構文をサポートしています。
- 比較が、プロパティ名と定数で使えます。少なくとも一つのオペランドは、プロパティ名でないといけません。
- 比較演算子 ==, <=, <, >=, >, !=, BETWEEN が、int, long, long long, float, double, NSDate で使えます。 Ex.)
age == 45
- オブジェクトの同一性 ==, != Ex.)
Results<Employee>().filter("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.)
Results<Company>().filter("ceo == nil")
- いずれかの要素が条件と一致するかどうかの ANY Ex.)
ANY student.age < 21
- 範囲が指定できる BETWEEN Ex.
Results<Person>.filter("age BETWEEN %@", [42, 43]])
詳しくは、Results().filter(_:...)
をご覧ください。
ソート
Results は、プロパティの値でソートすることができます。 たとえば、以下の例では、[RLMResults sortedResultsUsingProperty:ascending:]
によって、特定の Dog オブジェクトを name の値をアルファベット順でソートしてから取り出しています。
// Sort tan dogs with names starting with "B" by name
let sortedDogs = Realm().objects(Dog).filter("color = 'tan' AND name BEGINSWITH 'B'").sorted("name")
詳しくは、Results().filter(_:...)
と Results().sorted(_:ascending:)
をご覧ください。
連続したクエリの実行
他のデータベースと比較した時に、Realmを使う利点として、小さなオーバーヘッドで、連続してクエリが実行できる点が挙げられます。 たとえば、color = tan
で、 name
が B
からはじまる Dogオブジェクトをフェッチしたい場合、以下のように連鎖的にメソッドを呼び出すことができます。
let tanDogs = Realm().objects(Dog).filter("color = 'tan'")
let tanDogsWithBNames = tanDogs.filter("name BEGINSWITH 'B'")
Realms
デフォルトRealm
すでにお気付きだと思いますが、これまで Realm()
を呼ぶことで、変数 realm
を初期化してきました。 このメソッドは、アプリの Documents フォルダにある、“default.realm” ファイルの RLMRealm オブジェクトを返します。
Realm.defaultPath
を使うことで、デフォルト Realm へのパスを変更できます。この機能は、特にテスト時や iOS8 で利用可能の Shared Container で役立ちます。
その他のRealm
場合によっては、複数の Realm が必要な時があります。バンドルデータとして Realm を用意しておき、それを read-only な Realm として開きたい時なのです。詳しくは、Realm(path:)
と Realm(path:encryptionKey:error:)
をご覧ください。
[RLMRealm realmWithPath:]
で引数として渡すパスは、書き込み可能である必要があります。 Realmファイルの基本的な保存場所は、iOSでは、”Documents” フォルダ、OSXでは、”Application Support” フォルダです。 Apple の iOS Data Storage Guidelines に沿って設計してください。
In-Memory Realms
基本的には、Realm は、ディスクにデータを保存しますが、Realm(inMemoryIdentifier:)
を呼ぶことで、データをメモリーに保存する In-Memory な Realm オブジェクトを作ることができます。
let realm = Realm(inMemoryIdentifier: "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
let realm = Realm()
// Break up the writing blocks into smaller portions
// by starting a new transaction
for idx1 in 0..<1000 {
realm.beginWrite()
// Add row via dictionary. Property order is ignored.
for idx2 in 0..<1000 {
realm.create(Person.self, value: [
"name": "\(idx1)",
"birthdate": NSDate(timeIntervalSince1970: idx2)
])
}
// Commit the write transaction
// to make this data available to other threads
realm.commitWrite()
}
}
Realm間でのオブジェクトのコピー
Realm のオブジェクトを他の Realm へコピーするには、Realm().create(_:value:update:)
へ元のオブジェクトを引数として渡すことでできます。 例えば、Realm().create(MyObjectSubclass.self, value: originalObjectInstance)
のように使います。
初期データとしてのRealm
アプリをインストールした時からすぐに使えるような初期データをアプリに入れておくことは、とても一般的な方法です。以下が、Realmでのやり方です。
- はじめに、初期データの入った Realm を用意します。最終的にリリースする時と同じデータモデルを定義し、データを入れます。Realm ファイルは、クロスプラットフォームですので、OSX アプリや、シュミレータで動く iOS アプリでも問題ありません。(JSONImportをご覧ください)
- 初期データを入れるコードの終わりに、ファイルの圧縮コピーをするメソッドを使用し、Realm ファイルを圧縮してください。(
Realm().writeCopyToPath(_:encryptionKey:)
をご覧ください)このメソッドを使って Realm ファイルをコピーすると、Realm のファイルサイズを軽減でき、ユーザーがアプリをダウンロードするときに軽くなります。 - 新しく圧縮した Realm ファイルを、アプリの Xcode のプロジェクトナビゲーターにDrag&Dropします。
- Xcode の Build Phase タブで、”Copy Bundle Resources” に圧縮したRealmファイルを追加します。
- アプリから、追加した Realm ファイルがアクセスできるようになります。
NSBundle.mainBundle().pathForResource(_:ofType:)
を使って、パスを見つけることができます。 - これで、
Realm(path:readOnly:encryptionKey:error:)
で readOnly な Realm を作ることもできるし、その初期データを含んだ Realm に書き込みたい場合は、NSFileManager.defaultManager().copyItemAtPath(_:toPath:error:)
を使い、アプリのDocuments
ディレクトリにRealmファイルをコピーしてから、Realm(path:)
にパスを渡し、Realmインスタンスを作るようにしてください。
Realm ファイルを使っての初期データの組み込みは、migration sample appをご参照ください。
リレーションシップ
Object は、プロパティに Object, List を宣言することで、他のオブジェクトと関連付けを定義することができます。 List は、Array と似たようなインターフェイスを持ち、添え字アクセスでオブジェクトにアクセスすることもできます。Arrayと違う点は、List は、Object のみを含むことができるという点です。 詳しくは、List をご覧ください。
以前に Person モデルを作成したと思います。ここでは、Dog モデルを作成し関連付けを行ってみましょう。
class Dog: Object {
dynamic var name = ""
}
To-One
モデル間で多対一や一対一の関連性を持たせたい場合は、Object モデルクラスのプロパティを宣言します:
class Dog: Object {
... // other property declarations
dynamic var owner: Person?
}
以下のようにこのプロパティを使えます:
let jim = Person()
let rex = Dog()
rex.owner = jim
Object をプロパティで使うと、ネストされたプロパティにシンプルなシンタックスでアクセスができます。 たとえば、上記の例で rex.owner.address.country
とようにすると、自動的に必要なプロパティの値をフェッチされます。
To-Many
プロパティに List を宣言することで、対多の関連性を定義できます。List は、Object を含み、ミュータブルな Array と似たようなインターフェイスを持ちます。
Person クラスに Dog クラスの対多関連を持たせるとき List<Dog>
を定義します。
class Person: Object {
... // other property declarations
let dogs = List<Dog>()
}
これで、List プロパティに対してアクセス、オブジェクトを追加したりできるようになりました。
let someDogs = Realm().objects(Dog).filter("name contains 'Fido'")
jim.dogs.extend(someDogs)
jim.dogs.append(rex)
Inverse Relationships
逆方向のリレーションシップを張っておくことで、特定のプロパティを用いて、関連するオブジェクトへアクセスすることができます。 Object().linkingObjects(_:forProperty:)
を使い、Dog
クラス定義しておくことで、紐づくオブジェクトを取得できるようになります。 以下のように read-only の owners
プロパティを Dog
クラスに定義することで、逆のリレーションシップを張ることができます。
class Dog: Object {
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.self, forProperty: "dogs")
}
}
通知
Realm 内のデータの更新がかかる毎に、他の Realm インスタンスに通知を送り、それを受け取ることができます。 通知を受け取る方法は、以下のように事前にブロックとして登録しておく必要があります。
// Observe Realm Notifications
let token = realm.addNotificationBlock { notification, realm in
viewController.updateUI()
}
Notificaiton Token
が保持されてる限り、登録された Notification は存在し続けます。 更新などをするためにクラス上で Notification Token
の強参照を保持しておく必要があります。 Notification Token
が、解放された時点で登録された Notification は自動的に抹消されます。
詳しくは、Realm().addNotificationBlock(_:)
と Realm().removeNotificationBlock(_:)
をご覧ください。
マイグレーション
データベースを使ってる場合、時間が経つにつれ、データモデルというのは変更されていくものです。 Realm でのデータモデルは、シンプルな インターフェイスで定義されてますので、インターフェイスに変更を加えるだけで、簡単にデータモデルを変えられます。 たとえば、以下の Person
モデルについて考えてみてください。
class Person: Object {
dynamic var firstName = ""
dynamic var lastName = ""
dynamic var age = 0
}
ここで、firstName
と lastName
を一つにして、fullName
プロパティが必要になったとします。 そこで以下のような単純な変更をインターフェイスに加えることにします。
class Person: Object {
dynamic var fullName = ""
dynamic var age = 0
}
ここでのポイントは、もし以前に前のデータモデルでのデータが保存されている場合、新しく定義し直したデータモデルとディスクに保存されている古いデータモデルとの間に不都合が生じてしまいます。 マイグレーションを実行せずに Realm を使おうとすると、Exception が起こります。
マイグレーションの実行
マイグレーション処理は、setSchemaVersion(_:_:_:)
を呼ぶ時に引数として渡すブロックの中に定義します。 このとき、スキーマに関連しているバージョン毎にマイグレーション処理の分岐を書く必要があります。 マイグレーションブロックの中には、全ての古いデータモデルから新しいデータモデルへ移行させるためのロジックが書かれていないといけません。 setSchemaVersion(_:_:_:)
が呼ばれた後に、自動的にマイグレーションブロックが実行されます。
たとえば、上記の Person
クラスのマイグレーションについて考えてみましょう。 最低限必要なマイグレーション処理は、以下のようなものです:
// 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
setSchemaVersion(1, realmPath: Realm.defaultPath,
migrationBlock: { 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(_:_:_:)`, opening an outdated
// Realm will automatically perform the migration and opening the Realm will succeed
// i.e. Realm()
Realm によって自動的にスキーマが更新されることを示すためにここでは、空のブロックでマイグレーションを実行しています。
これは最低限のマイグレーションですが、おそらく何かデータを新しいプロパティ(ここでは fullName
)に入れるために、ここに処理を記述すると思います。 マイグレーションブロックの中では、特定の型の列挙処理を行うために Migration().enumerate(_:_:)
を呼ぶことができます。 下記では、必要なマイグレーションロジックを適用しています。 変数 oldObject
を使って既にあるデータにアクセスし、新しく更新するデータには変数 newObject
を使ってアクセスしています。
// Inside your application(application:didFinishLaunchingWithOptions:)
setSchemaVersion(1, realmPath: Realm.defaultPath,
migrationBlock: { migration, oldSchemaVersion in
if oldSchemaVersion < 1 {
// The enumerate(_:_:) method iterates
// over every Person object stored in the Realm file
migration.enumerate(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
class Person: Object {
dynamic var firstName = ""
dynamic var firstName = ""
dynamic var age = 0
}
// v1
class Person: Object {
dynamic var fullName = "" // new property
dynamic var age = 0
}
// v2
class Person: Object {
dynamic var fullName = ""
dynamic var email = "" // new property
dynamic var age = 0
}
ここでのマイグレーションブロックの中に書くロジックは、以下のようになります。
setSchemaVersion(1, realmPath: Realm.defaultPath,
migrationBlock: { migration, oldSchemaVersion in
// The enumerateObjects:block: method iterates
// over every 'Person' object stored in the Realm file
migration.enumerate(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 = Realm()
詳しくは、migration sample app をご覧ください。
Linear Migrations
二人のユーザーがいると考えてください。 JP と Tim です。 JP は、よくアプリのアップデートをします。しかし、Tim は、いくつかのバージョンをスキップすることがあります。 JP にとっては、全てのバージョンをインストールしてることになるので、段階を踏んで全てのスキーマのアップデートを適用していることになります。 V0 から V1, V1 から V2 のように彼のアプリはアップデートしていきます。 それとは対照的に、Tim の場合は、V0 から V2 といきなりバージョンをすっ飛ばす恐れがあります。 マイグレーションブロックの中では、たとえどのバージョンのスキーマから始めたとしても、ネストしていない if (oldSchemaVersion < X)
で必ず必要なアップデート処理は行われなければなりません。
また、他のケースで、ユーザーがバージョンのスキップをしてアプリのアップデートをしたとします。もしあなたが、”email” プロパティをバージョン2で一度、削除し V3 で再び定義したとします。 そして、ユーザーは V1 から V3 へと、V2 をスキップしてアップデートした場合、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
let key = NSMutableData(length: 64)!
SecRandomCopyBytes(kSecRandomDefault, UInt(key.length),
UnsafeMutablePointer<UInt8>(key.mutableBytes))
// Open the encrypted Realm file
var error: NSError?
let realm = Realm(path: Realm.defaultPath,
readOnly: false, 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 = realm.objects(Dog).filter("name contains 'Fido'")
この機能を使用すると、ディスクに保存されるデータがAES-256で暗号化され、必要に応じ、複合化されます。 また、その後、Realm
インスタンス作成時には、同じ暗号化キーが必要になります。インスンタス作成後は、メモリ上に暗号化キーを保持され、指定したパス先にある Realm ファイルへアクセスは、自動的に暗号化キーが使用されるようになります。 たとえば、convenience メソッドを使って、defaultRealm を指定するコードは以下のようになります。
// 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
Realm.setEncryptionKey(key, forPath: Realm.defaultPath)
// Use the Realm as normal
let dogs = realm.objects(Dog).filter("name contains 'Fido'")
詳しくは、暗号化のサンプルコードをご覧ください。暗号化キーの作り方、キーチェーンへの安全な保存方法、Realm へのキーの渡し方などが分かるかと思います。
暗号化した Realm を使う場合、通常よりも10%未満パフォーマンスが下がります。
サードパーティのクラッシュレポート(Crashlytics や PLCrashReporter など)は、暗号化した Realm を初めて開く前に登録する必要があります。そうしないと、アプリが実際にクラッシュしていないときに誤ったエラーレポートを受け取る可能性があります。
デバッグ
Realm Swift API を使っている場合、アプリのデバッグは LLDB コンソールを使って行われなければなりません。
注意することとして、Realm の変数を調べる LLDBスクリプトを Xcode Plugin 経由でインストールされていても Swift の場合、これは上手く機能せず、不正なデータが表示されると思います。代わりに po
コマンドで Realm に保存されたデータのデバッグを行ってください。
テスト
Realmを使ったアプリのテスト
defaultRealm のパスの変更
Realmを使うまたは、テストをする最も簡単な方法は、defaultRealm を使用することです。テストコードを実行する前に、Realm.defaultPath = "/path/to/file.realm"
でテスト用の defaultRealm のパスを変更し、アプリで使用するデータを上書きすることを避けれます。
RLMRealm の受け渡し
また、テストする部分としては、引数として Realm のインスタンスが渡されるメソッドなどです。実際にアプリ実行時とテスト実行時で、異なる Realm インスタンスを投げても問題ないかテストします。 例えば、以下のテストコードは、JSON APIから GET を使ってユーザー情報を取得し、正常にデータが作成されているかをテストしています。
// Application Code
static func updateUserFromServer() {
let url = NSURL(string: "http://myapi.example.com/user")
NSURLSession.sharedSession().dataTaskWithURL(url) { data, _, _ in
self.createOrUpdateUserInRealm(Realm(), withData: data)
}
}
public static func createOrUpdateUserInRealm(realm: Realm, withData data: NSData) {
let object: [String: String] =
NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)
realm.write {
realm.create(object, update: true)
}
}
// Test Code
func testThatUserIsUpdatedFromServer() {
let testRealm = Realm(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(realm.objects(User).first!,
expectedUser,
"User was not properly updated from server.")
}
Realm.framework とテストターゲットのリンクを避ける
ダイナミックフレームワークとして Realm を使用している場合、ユニットテストターゲットが Realm を見つけれるようにしておく必要があります。 そのためには、ユニットテストの “Framework Search Paths” に RealmSwift.framework
を追加します。
テスト実行時に “Object type ‘…’ not persisted in Realm” というエラーが出る場合、RealmSwift.framework
がテストターゲットにリンクされていることが原因です。 CocoaPods をお使いの場合は、以下のように Podfile をお書きください。
target 'MyApp'
pod 'Realm'
end
target 'MyAppTests', exclusive: true do
pod 'Realm/Headers'
end
モデルクラスファイルをアプリのターゲットかフレームワークのターゲットにどちらか一方のみにリンクさせておきます。 そうでないと、テスト時に重複が生じ問題が発生する可能性があります。 詳しくは こちら をご覧下さい。
テストするコードは、全てテストターゲットからアクセス可能である必要があります。(Swiftでは public
修飾子をお使いください)
テストの状態の再設定
一般に、それぞれのユニットテストが独立していることかと思います。Realm ファイルをテスト毎にディスクから消し、また新しく作成することをお勧めいたします。以下のコードでは、XCTest での setUp
メソッド と tearDown
メソッド を使って再設定を行っています:
// Helpers
let realmPathForTesting = ""
func deleteRealmFilesAtPath(path: String) {
let fileManager = NSFileManager.defaultManager()
fileManager.removeItemAtPath(path, error: nil)
let lockPath = path + ".lock"
fileManager.removeItemAtPath(lockPath, error: nil)
}
// In XCTestCase subclass:
override func setUp() {
super.setUp()
deleteRealmFilesAtPath(realmPathForTesting)
Realm.defaultPath = realmPathForTesting
}
override func tearDown() {
super.tearDown()
deleteRealmFilesAtPath(realmPathForTesting)
}
Realmのテスト
Realm 自体は、全てのコミットにおいて広い範囲のテストが行われています。 Realm は オープンソース で開発されており、私たちはあなたのコントリビュートを喜んで歓迎いたします。
REST API
Realmでは、簡単に REST API と統合ができ、いくつかの利点があります。
- REST API のみだと常にネットとの接続が必要ですが、データをキャッシュすることでオフラインでも、アプリを動作させることができます。
- 完全にデータをキャッシュさせておくことで、
REST API
だけでは、不可能なローカルでクエリを実行したり、データを閲覧したりできます。 - データを保存させておくことで、サーバーサイド側での処理を新しい変更だけをロードするものだけに減らすことができます。
ベストプラクティス
- 非同期リクエスト — リクエストを投げる時や、他のブロッキング操作は、UI スレッドを止めてしまう恐れがあるので、バックグラウンド処理で行うことが推奨されています。これと同じ理由で、Realm での膨大なデータの変更は、バックグラウンド処理で行うべきです。バックグラウンドで行われた変更に対応するには、Notifications 機能を使いましょう。
- 膨大なデータのキャッシュ - 可能な時に事前にデータを Realm にフェッチし保存させておくことをオススメします。そのようにしておくと、いつでもローカルで保存してあるデータに対してクエリが投げられます。
- 挿入と更新 - Primary キーのような単一な識別子をデータセットが持っている場合、
Realm().add(_:update:)
を使うことで、REST API からレスポンスを受け取ったときに、より簡単にデータを挿入/更新することができます。このメソッドは、更新が必要かどうかや、既に存在するデータかどうかなどを自動的にチェックしてくれます。
Example
以下の例は、シンプルな Realm と REST API との連携例です。この例は、Foursquare API から JSON データをフェッチしてき、Default Realm に Realm Object として保存している例です。 これに似たようなユースケース例として参考に、この ビデオ をご覧ください。 まず、Default Realm のインスタンスを生成します。そして、API からデータをフェッチしてきます。 より単純にするためにここでは、[NSData initWithContentsOfURL]
を使っています。
// 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) as! NSDictionary)["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 にインポートする方法は、いくつか存在します。カスタムメソッドを作り、手動で Object のそれぞれのプロパティに配置することもできます。 この例の見どころは、直接 NSDicionary に挿入する代わりに、自動的に Object を作っているところです。 これをきちんと動作させるためには、Object のプロパティの構造と、JSON の key を完全に一致させておく必要があります。 もし、それらが違っている場合、そのプロパティまたは key は無視されます。以下の Object の定義では、問題なく動作します。
class Contact: Object {
dynamic var phone = ""
static func primaryKey() -> String? {
return "phone"
}
}
class Location: Object {
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: Object {
dynamic var id = ""
dynamic var name = ""
dynamic var contact = Contact()
dynamic var location = Location()
static func primaryKey() -> String? {
return "id"
}
}
結果のデータが配列として返ってくるので、Realm().create(Venue.self, value: value)
を毎回呼びオブジェクトを作らなければいけません。 この例は、JSON データから Venue
オブジェクトを複数作り Default Realm に追加してます。
//Extract the array of venues from the response
let venues = json["venues"] as! [NSDictionary]
let realm = Realm()
realm.write {
// Save one Venue object (and dependents) for each element of the array
for venue in venues {
realm.create(Venue.self, value: venue, update: true)
}
}
次のステップ
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 の仕組みがあります。KVO のサポートも実装予定です: GitHub issue #601
複数プロセスからのRealmの利用
もちろん、複数のスレッドから同時にRealmファイルへアクセスすることはできますが、シングルプロセスからのみ利用可能です。 これは、主に、iOS8のExtentionsとOSXアプリケーションでの開発に影響します。複数のプロセスで同じRealmファイルを使う場合は、Realmファイルをコピーするか、もしくは、新しいRealmファイルを作成してください。 マルチプロセスサポートは、近々、追加するする予定です。
ファイルサイズと中間バージョンについて
誰もが、SQLiteを使ってデータを保存した時よりもディスクの使用容量が少なくなることを期待されることかと思います。 Realmファイルの容量が非常に大きくなった場合、Realmがオブジェクトをトラッキングするために中間データを作っているためです。 invalidate
を呼ぶことで、Realmオブジェクトを解放してください。 データの一貫性を保つために、データをフェッチしたときのみバージョンがアップデートされます。 このことは、Realmからデータを読み込み、別のスレッドでそのデータの更新が行われた場合、読み込まれたデータの情報はアップデートされないということです。 そして、Realmは、この中間バージョンを管理するために、内部でのみ使用されるデータが増えていき、その結果、ディスクの使用領域が肥大化してしまうことになります。 (この余分なスペースは、今後の書き込みで再度、使われるか、圧縮されることになります。例えば、writeCopyToPath:error
が呼ばれた時などです)
その代わりに、invalidate
を呼ぶことで、Realmにこれまでに読んできたデータは必要なく、オブジェクトの中間バージョンは必要ないと伝え、解放してあげてください。
Objective‑CからListプロパティへのアクセスはできません
Realm Swift を Objective‑C から使用する場合、List
プロパティを private
か internal
にする必要があります。 これは現在、確認されている Swift のバグで自動で生成される Objective‑C ヘッダー(-Swift.h
)へコンパイルすることができません。 GitHub issue #1925 もご覧ください。
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 として利用可能です。