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

Swiftだけを使用した(Objective‑Cを使わない)プロジェクトでRealmを利用する場合は、Realm Swiftを使うことをご検討ください。よりSwiftらしい記述ができます。 Realm Objective‑CとRealm Swiftを併用することはできません。

Realm Objective‑Cはアプリケーションのモデル層を効率的に安全で迅速な方法で記述することができます。 下記の例をご覧ください:

// 通常のObjective‑Cのクラスと同じように定義します
@interface Dog : RLMObject
@property NSString *name;
@property NSData   *picture;
@property NSInteger age;
@end
@implementation Dog
@end
RLM_ARRAY_TYPE(Dog)
@interface Person : RLMObject
@property NSString             *name;
@property RLMArray<Dog *><Dog> *dogs;
@end
@implementation Person
@end

// 通常のObjective‑Cのオブジェクトと同じように扱えます
Dog *mydog = [[Dog alloc] init];
mydog.name = @"Rex";
mydog.age = 1;
mydog.picture = nil; // プロパティにはnilも代入できます
NSLog(@"Name of dog: %@", mydog.name);

// 2歳未満のDogオブジェクトを検索します
RLMResults<Dog *> *puppies = [Dog objectsWhere:@"age < 2"];
puppies.count; // => 0 (この時点では、Dogオブジェクトはまだ1件も保存されていません)

// データを永続化するのはとても簡単です
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
  [realm addObject:mydog];
}];

// クエリの実行結果は自動的に最新の状態に更新されます
puppies.count; // => 1

// バックグラウンドスレッドで検索を実行し、値を更新します
dispatch_async(dispatch_queue_create("background", 0), ^{
  @autoreleasepool {
    Dog *theDog = [[Dog objectsWhere:@"age == 1"] firstObject];
    RLMRealm *realm = [RLMRealm defaultRealm];
    [realm beginWriteTransaction];
    theDog.age = 3;
    [realm commitWriteTransaction];
  }
});

Core Dataからの移行を検討している場合は、こちらの記事(英文)が参考になります。RealmからCore Dataへの移行に必要なステップが記載されています。

はじめに

Download Realm for Objective‑C

ソースコードはGitHubにて公開しています。

必要条件

  • iOS 7以降、またはmacOS 10.9以降、およびすべてのバージョンのtvOSとwatchOS。
  • Xcode 8.0以降が必要です。Realm Objective‑C 2.3.0がSwift 2.xとXcode 7.3をサポートする最後のバージョンです。

インストール

注意: Dynamic frameworkはiOS 7では利用できません。iOS 7をサポートする場合は“Static Framework”の説明をご覧ください。

  1. こちらから最新版のRealmをダウンロードしてzipファイルを展開してください。
  2. Xcodeでプロジェクトをクリックしプロジェクト設定の“General”タブを表示します。展開したフォルダの中にあるios/watchos/tvos/、またはosx/フォルダにあるdynamic/フォルダからRealm.frameworkを“Embedded Binaries”にドラッグ&ドロップしてください。このとき、Copy items if neededにチェックが入ってることを確認してからFinishをクリックしてください(1つのプロジェクトで複数のプラットフォームに対応する場合を除く)。
  3. ユニットテストのターゲットを選択して“Build Settings”タブの“Framework Search Paths”にRealm.frameworkの親フォルダのパスを追加してください。
  4. Swiftで利用する場合は、さらにSwiftフォルダにあるRLMSupport.swiftも同様にファイルナビゲータエリアにドラッグ&ドロップし、Copy items if neededをチェックして追加してください。
  5. iOS、watchOSまたはtvOSのプロジェクトで利用する場合は、アプリケーションのターゲットの“Build Phases”タブで新しく“Run Script Phase”を追加し、以下のスクリプトをそのままコピー&ペーストしてください。

    bash "${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework/strip-frameworks.sh"

この手順はアプリケーションを申請する際のiTunes Connectの不具合を回避するために必要です。

  1. CocoaPods 0.39.0以降をインストールしてください
  2. 最新のRealmをインストールするためにコマンドラインからpod repo updateを実行して、CocoaPodsのSpecリポジトリを更新してください。
  3. Podfileのアプリケーションのターゲットに対してpod 'Realm'、テストターゲットに対してpod 'Realm/Headers'と追加してください。
  4. コマンドラインでpod installを実行してください。
  5. CocoaPodsによって作られた.xcworkspaceファイルを開いてください。
  6. SwiftからRealmを利用する場合(Objective-CとSwiftを混ぜて使う場合)は、Swift/RLMSupport.swiftファイルをXcodeプロジェクトのファイルナビゲータにドラッグしてコピーしてください。その際、Copy items if neededチェックボックスを選択するのを忘れないように気をつけてください。
  1. Carthage 0.17.0以降をインストールしてください
  2. Cartfileにgithub "realm/realm-cocoa"と追加してください。
  3. コマンドラインでcarthage update を実行してください。
  4. Realm.frameworkCarthage/Build/ディレクトリ内の各プラットフォームのディレクトリから適切なものを選択し、Xcodeの“General”タブにある“Linked Frameworks and Libraries”にドラッグしてください。
  5. iOS/tvOS/watchOS: アプリケーションのターゲットの“Build Phases”の“+”ボタンをクリックし、以下のような“New Run Script Phase”を追加します。

    /usr/local/bin/carthage copy-frameworks

    このとき、“Input Files”にはフレームワークのパスを指定します。

    例:

    $(SRCROOT)/Carthage/Build/iOS/Realm.framework

    この手順はアプリケーションを申請する際のiTunes Connectの不具合を回避するために必要です。

  6. RealmをSwiftからも利用する場合、Swift/RLMSupport.swiftファイルをファイルナビゲータにドラッグ&ドロップしてプロジェクトに追加してください。 その際、Copy items if neededのチェックボックスをチェックするのを忘れないようにしてください。
  1. こちらから最新版のRealmをダウンロードしてzipファイルを展開してください。
  2. 展開したフォルダの中にあるios/static/フォルダからRealm.frameworkをXcodeのファイルナビゲータエリアにドラッグ&ドロップしてください。このとき、Copy items if neededにチェックが入ってることを確認してからFinishをクリックしてください。
  3. Xcodeのファイルナビゲータエリアでプロジェクトをクリックし、アプリケーションのターゲットを選択します。Build PhasesタブのLink Binary with Librariesにある“+”ボタンをクリックして、libc++.tbdlibz.tbdを追加してください。
  4. Swiftで利用する場合は、さらにSwift/フォルダにあるRLMSupport.swiftも同様にファイルナビゲータエリアにドラッグ&ドロップし、Copy items if neededをチェックして追加してください。

Realmフレームワークをインポートする

Realmをインポートし、コードから利用できるようにするために、Realmを使いたい各ソースファイルの先頭に#import <Realm/Realm.h>と記述してください。もし、Swiftのソースファイルがあるなら、そちらにはimport Realmと記述してください。これで使い始めるための準備はすべて完了です!

Realm Objective‑CをSwiftから使う場合

Swiftだけを使用した(Objective‑Cを使わない)プロジェクトでRealmを利用する場合は、Realm Swiftを使うことをご検討ください。よりSwiftらしい記述ができます。

Realm Objective‑CはObjective‑CとSwiftの両方の言語を混ぜて利用しているプロジェクトでも動くように設計されています。Swiftからでも、例えばモデルを定義するなどのObjective‑Cからできることはすべて可能です。ですが、ほんの少し純粋なObjective‑Cプロジェクトの場合とは異なるやり方をしなければならない場合があります。それを下記に示します。

RLMSupport.swift

Swift/RLMSupport.swiftファイル(Realm Objective‑CのZipファイルにもこのファイルは含まれています。)をプロジェクトに組み込んで使うことをおすすめします。

このファイルを追加することによって、Realm Objective‑Cのコレクション型がSwiftのSequenceに準拠するようになります。また、標準ではSwiftからは利用できない可変長引数を含むメソッドが利用できるようになります。

このファイルを標準でRealm Objective‑Cに含めてしまうと、Swiftを使わないプロジェクトでもSwiftのランタイムライブラリが組み込まれてしまうので、標準ではRealm Objective‑Cにこのファイルは含まれません。

RLMArrayプロパティ

Realm Objective‑Cでは1対多の関連を示すRLMArrayのプロパティに格納される要素の型を判断するために、プロトコルを利用しています。Swiftでは同じ書き方はできません。その代わりに、RLMArrayプロパティを定義するときは、次のように書いてください。

class Person: RLMObject {
  dynamic var dogs = RLMArray(objectClassName: Dog.className())
}

上記は下記のObjective‑CにおけるRLMArrayの定義とまったく同一です。

@interface Person : RLMObject
@property RLMArray<Dog *><Dog> *dogs;
@end

tvOS

tvOSではDocumentsディレクトリへの書き込みは禁止されているので、tvOS用のフレームワークでは、デフォルトの保存先はNSCachesDirectoryに変わっています。ただし、キャッシュディレクトリ内のファイルは常にシステムによって削除される可能性があるので注意してください。そのため、tvOSにおけるRealmの利用は、復元可能なデータのキャッシュなどに利用し、消えては困る重要な(再生成できない)ユーザーデータなどを保存するのは避けてください。

エクステンション(例: Top Shelf)とtvOSアプリの間でデータを共有するためにApp Groupコンテナを利用する場合は、下記のようにコンテナURL内の/Library/Caches/ディレクトリを使用してください。それ以外のディレクトリではtvOSの制限により、Realmに必要なファイルを作成することができません。

// end declarations
RLMRealmConfiguration *configuration = [RLMRealmConfiguration defaultConfiguration];
configuration.fileURL = [[[NSFileManager defaultManager]
    containerURLForSecurityApplicationGroupIdentifier:@"group.io.realm.examples.extension"]
    URLByAppendingPathComponent:@"Library/Caches/default.realm"];

別の利用方法として、Realmファイルをコンテンツデータとしてアプリケーションにバンドルする使い方も便利です。tvOSアプリケーションのサイズはApp Storeガイドラインにて200MB以下と規定されているのでバンドルするデータの容量に注意してください。

上記のキャッシュとしての使い方と、データをアプリケーションにバンドルする使い方について、両方のサンプルコードを提供しています。利用する際の参考にしてください。

Realm Browser

RealmBrowserは、.realmデータベースを閲覧、編集するMac アプリケーションです。

Realm Browser

また、Tools > Generate demo database と選択すると、サンプルデータを含むテスト用のRealmデータベースを作ることができます。

開発中のアプリのRealmファイルがどの場所に格納されているかは、このStackOverflowの回答が参考になります。

Realm BrowserMac App Store からダウンロードできます。

Xcode Plugin

Xcodeプラグインを使うことでRealmモデルファイルの作成が簡単になります。

このXcodeプラグインをインストールする一番簡単な方法は、Alcatrazで“RealmPlugin”と検索することです。

また、手作業でインストールする場合は、最新版のRealmplugin/RealmPlugin.xcodeprojをビルドすることでインストールができます。

プラグインをインストール後にXcodeを再起動すると、Xcodeで新しくファイルを作成時(File > New > File...または⌘N)にRealm Modelという選択肢が追加されています。

APIリファレンス

Realmで使用できるすべてのクラスとメソッドに関しては、APIリファレンスをご覧ください。

サンプルコード

最新版のRealmexamplesフォルダにiOSとOS Xそれぞれのサンプルコードがあります。 マイグレーション、UITableViewControllerと組み合わせた使い方、暗号化やコマンドラインツールなど、さまざまな機能が紹介されています。ぜひ、ご参考にしてください。

ヘルプ

  • 使い方に困ったときは、StackOverflowで#realmタグを付けて質問してください。私たちは毎日StackOverflowをチェックしています。
  • さらに複雑な問題に対する質問は、こちらの Slackチャットにて聞いてください。(質問は日本語で構いません)
  • バグ報告や機能リクエストについては GitHubのIssuesにご報告ください。
  • Community Newsletterに参加することで定期的にRealmに関するTipsやユースケース、ブログの更新やチュートリアルなど、Realm の最新情報が届きます。

モデル

Realmのデータモデルは、普通のObjective‑Cのクラスとして定義できます。単に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はアプリケーションの起動時に、定義されたすべてのモデルクラスを解析しています。まったく使われていないクラスも含めて、すべてのモデルクラスは正しく定義されていなければなりません。

RealmSwiftではモデルの情報を取得するためにSwift.reflect(_:)関数を利用しています。 そのため、モデルに対してイニシャライザinit()の呼び出しが必ず成功する必要があります。 非Optionalなプロパティにおいて初期値を必ず指定しなければならないのはこの理由によります。

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

対応しているデータ型

Realmでは、次に示すデータ型をサポートしています: BOOLboolintNSIntegerlonglong longfloatdoubleNSStringNSDateNSDataおよび各データ型をラップしたNSNumber

CGFloat型はプラットフォーム(CPUアーキテクチャ)によって実際の定義が変わるため、使用しないようにしてください。

1対1や、1対多のようなリレーションシップのためにRLMArray<Object *><Object>RLMObject のサブクラスのプロパティも定義できます。

RLMArrayはObjective‑Cジェネリクスをサポートしています。下記にプロパティ定義の構成要素がそれぞれどのような意味を持つのかを示します:

  • RLMArray: プロパティの型を示します。
  • <Object *>: ジェネリクスの型引数です。コンパイル時に間違った型のオブジェクトが格納されるのを防ぎます。
  • <Object>: RLMArrayが準拠しているプロトコルです。実行時にRealmが型を判別できるようにします。

リレーションシップ(関連)

RLMObjectはプロパティにRLMObjectRLMObjectを使用して、他のオブジェクトとの関連を定義することができます。

RLMArrayNSArrayとよく似たAPIを持ち、添え字を使って各要素にアクセスすることもできます。

NSArrayと異なる点は、RLMArrayRLMObjectのサブクラスのみ格納することができるということです。

詳しくは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対多の関連を定義するにはRLMObjectを持つプロパティを定義します。RLMObjectは別のRLMObjectを含み、NSMutableArrayとよく似たAPIを持ちます。

RLMArrayオブジェクトは同じ(プライマリキーが同じオブジェクトであっても)Realmオブジェクトを複数格納できます。例えば、最初に空のRLMArrayオブジェクトを作り、そのあと同じオブジェクトを3回に渡って追加したとします。するとRLMArrayオブジェクトには同じオブジェクトがそれぞれ0番目、1番目、2番目に格納されます。

PersonクラスがDogクラスを1対多の関連として持つとします。まず、RLMArray<Dog>を定義します。 これは下記のように、関連するクラス定義の下に専用のマクロを使って定義します。

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

RLM_ARRAY_TYPE(Dog) // RLMArray<Dog>型の定義

マクロRLM_ARRAY_TYPEはプロトコルを定義するコードに展開されます。これはプロトコルの文法を利用してRLMArray<Dog>のように型パラメータの指定(っぽいこと)を 可能にするためです。このため、このマクロが@interface定義のすぐ下に記述されてない場合は、マクロに渡す型をコンパイラが解決できるように、前方参照(@class)を利用しなければならないことがあります。

これでRLMArray<Dog>型のプロパティを定義できるようになりました。

// Person.h
@interface Person : RLMObject
// ... other property declarations
@property RLMArray<Dog *><Dog> *dogs;
@end

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

// 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];

注意することとして、RLMArrayのプロパティにはnilを代入することが可能ですが、それはRLMArrayを空にすることを意味していて、削除することにはなりません。つまり、nilを代入したとしても、空になるだけなので、RLMArrayはいつでもオブジェクトを追加することが可能です。

逆方向の関連

Realmの関連は一方通行です。Dogへの1対多の関連であるPerson.dogsのプロパティと、Personへの1対1の関連であるDog.ownerの2つのプロパティは、それぞれ互いに独立して動作します。Person.dogsプロパティにDogオブジェクトを追加しても、Dog.ownerプロパティにPersonのオブジェクトは自動的に追加されたりはしません。手作業でこの2つの関連の整合性を保とうとすることは、間違いを起こしやすく、複雑で冗長さを招くので、Realmでは下記に示すような、逆方向の関連(バックリンクとも呼ばれます)を表現するためのプロパティを使用することができます。

Linkng Object型のプロパティを使用することで、関連元のオブジェクトを特定のプロパティを使って取得することができます。例えば、Dogクラスにownersという自分自身を関連として保持しているPersonオブジェクトをすべて取得するというプロパティを持たせることができます。その方法はownersプロパティをRLMLinkingObjects型として定義し、+[RLMObject linkingObjectsProperties]メソッドをownersPersonクラスを返すことを示すようにオーバーライドするだけです。

@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@property (readonly) RLMLinkingObjects *owners;
@end

@implementation Dog
// Define "owners" as the inverse relationship to Person.dogs
+ (NSDictionary *)linkingObjectsProperties {
  return @{
    @"owners": [RLMPropertyDescriptor descriptorWithClass:Person.class propertyName:@"dogs"],
  };
}
@end

Optional型のプロパティ

NSStringNSDataおよびNSDate型のプロパティはデフォルトでnilを設定することが可能です。 もしnilを設定することを禁止し、常に値が存在することを保証したい場合は、+requiredPropertiesメソッドをオーバーライドします。

例えば、下記のモデル定義では、Personオブジェクトのnameプロパティにnilを設定して保存しようとすると例外が発生します。しかしbirthdayプロパティにはnilを設定して保存することができます。

@interface Person : RLMObject
@property NSString *name;
@property NSDate *birthday;
@end

@implementation Person
+ (NSArray *)requiredProperties {
    return @[@"name"];
}
@end

数値型のプロパティをOptional(nil可)として扱うにはNSNumberでラップします。Realmは各数値のサイズによって異なる内部表現を使用しているため、ラップする実際の型をRLMIntRLMFloatRLMDoubleRLMBoolを使って指定する必要があります。代入された値は指定の型に変換されます。

NSDecimalNumber型の値についてはRLMDoubleプロパティに対してだけセットできることには注意が必要です。そしてRealmは単に値をDoubleの精度で保存するのでNSDecimalNumberが持つ固定10進数の精度は失われてしまいます。

例えば、Personオブジェクトについて、誕生日(birthday)ではなく年齢(age)を保存するとします。その場合でも年齢が不明な場合はageプロパティをnilにできます。

@interface Person : RLMObject
@property NSString *name;
@property NSNumber<RLMInt> *age;
@end

@implementation Person
+ (NSArray *)requiredProperties {
    return @[@"name"];
}
@end

RLMObjectのサブクラスのプロパティは常にnilにできます。そのため、requiredPropertiesに指定することはできません。RLMArray型のプロパティはnilにすることはできません。

チートシート(早見表)

以下はプロパティ定義のデータ型ごとの早見表です。

Type 非Optional Optional
Bool @property BOOL value; @property NSNumber<RLMBool> *value;
Int @property int value; @property NSNumber<RLMInt> *value;
Float @property float value; @property NSNumber<RLMFloat> *value;
Double @property double value; @property NSNumber<RLMDouble> *value;
String @property NSString *value; 1 @property NSString *value;
Data @property NSData *value; 1 @property NSData *value;
Date @property NSDate *value; 1 @property NSDate *value;
Object n/a: 非Optionalにはできません @property Object *value;
List @property RLMArray<Object *><Object> *value; n/a: Optionalにはできません
LinkingObjects @property (readonly) RLMLinkingObjects<Object *> *value; 2 n/a: Optionalにはできません

1) Objective‑Cの参照型(NSString、NSDate、 NSData)を非Optionalとして定義するには、下記のメソッドを使用してプロパティを非Optionalに指定する必要があります。

@implementation MyModel
+ (NSArray *)requiredProperties {
    return @[@"value"];
}
@end

2) Linking objects properties have to be declared in combination with a +linkingObjectsProperties method:

@implementation MyModel
+ (NSDictionary *)linkingObjectsProperties {
    return @{ @"property": [RLMPropertyDescriptor descriptorWithClass:Class.class propertyName:@"link"] };
}
@end

プロパティ属性

RealmではnonatomicatomicstrongcopyweakのようなObjective‑Cのプロパティ属性は無視されます。 Realmの内部では独自の最適化された仕組みが使われます。 そのため、混乱を避けるために、Realmモデルクラスを定義するときには一切のプロパティ属性を付けないことを推奨しています。

ただし、プロパティ属性を付けた場合はRLMObjectがRealmに保存されるまでは有効です。 setterまたはgetter属性を利用してセッター/ゲッターメソッドの名前を変えた場合は、RLMObjectがRealmに管理されている(Managed)かどうかにかかわらず、変更後の名前を使うことができます。

なぜなら、まだRealmに保存されていない(Unmanaged)オブジェクトは通常のCocoaオブジェクトと同等だからです。プロパティの属性は通常のCocoaオブジェクトと同様に振舞います。

Realm Objective-CをSwiftから利用する場合は、モデルのプロパティにdynamic var属性が必要です。

インデックス付きプロパティ

クラスメソッドの+indexedPropertiesをオーバーライドすることで、インデックスに追加するプロパティを指定できます:

@interface Book : RLMObject
@property float price;
@property NSString *title;
@end

@implementation Book
+ (NSArray *)indexedProperties {
  return @[@"title"];
}
@end

文字列と整数型、Bool型およびNSDate型のプロパティはインデックスに対応しています。

プロパティをインデックスに登録することは=INを使ったクエリの速度を大幅に向上します。その代わりにオブジェクトを作成する速度は少し遅くなります。

初期値

クラスメソッドの+defaultPropertyValues をオーバーライドすることで、プロパティの初期値を指定できます。

@interface Book : RLMObject
@property float price;
@property NSString *title;
@end

@implementation Book
+ (NSDictionary *)defaultPropertyValues {
    return @{@"price" : @0, @"title": @""};
}
@end

オブジェクトの自動更新(ライブアップデート)

RLMObjectのインスタンスは常に最新の内部データの状態に自動的に更新されています。つまり、オブジェクトをいちいち再読み込みする必要はありません。プロパティを変更すると、その変更はただちに同じオブジェクトを参照している他のインスタンスに反映されます。

Dog *myDog = [[Dog alloc] init];
myDog.name = @"Fido";
myDog.age = 1;

[realm transactionWithBlock:^{
  [realm addObject:myDog];
}];

Dog *myPuppy = [[Dog objectsWhere:@"age == 1"] firstObject];
[realm transactionWithBlock:^{
  myPuppy.age = 2;
}];

myDog.age; // => 2

このようなRLMObjectの性質は、Realmが高速にかつ効率的に動作するだけでなく、アプリケーションのコードをよりシンプルに、リアクティブにします。例えば、あるRealmオブジェクトのデータを表示しているUIコンポーネントがあるとした場合、そのUIコンポーネントを再描画する前に、いちいち再読み込みしたり、検索し直す必要はありません。

Realmの通知を監視することで、いつRealmのデータが更新されたのかが分かります。データが更新されたタイミングでアプリケーションのUIを更新するべきでしょう。別の方法として、キー値監視(KVO)を用いて、特定のRLMObjectのプロパティの変更を知ることができます。

プライマリキー

クラスメソッドの+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

保存しないプロパティは通常のObjective-C、Swiftのオブジェクトのプロパティと 完全に同じ ものです。Realmが持つ便利な機能は一切サポートされません。例えば、クエリの条件に指定することはできません。値の自動更新も適用されません。また、Realmの通知によって監視することもできません。保存しないプロパティの変更を監視したい場合はKVOを使用します。

モデルクラスの継承

複数のモデル間で共通のコードを再利用するために、モデルクラスをさらに継承してサブクラスを作ることができます。しかし、一般的なCocoaフレームワークのクラスを継承したときのようなポリモーフィズム(多態性)は機能しません。できることは下記に限られます:

  • スーパークラスのクラスメソッド、インスタンスメソッド、およびプロパティはサブクラスに継承されます。
  • スーパークラスを引数としてとるメソッドと関数はサブクラスから操作できます。

下記の挙動はサポートされません:

  • 継承関係にあるクラス間のキャスト(例: サブクラスからサブクラス、サブクラスからスーパークラス、スーパークラスからサブクラスなど)
  • (スーパークラスを指定して)複数のサブクラスを一度に検索する
  • 複数の異なるクラスをコンテナオブジェクト(RLMArrayRLMResults)に格納する。

このような多態性のサポートは機能のロードマップに予定されています。それまでの間の一時的な対応策としていくつかのサンプルコードを提供しています。

あるいは、(実装の都合が許すなら)サブクラスよりコンポジションを使って共通のロジックを別のクラスに持たせることを推奨します:

// 元になるモデル
@interface Animal : RLMObject
@property NSInteger age;
@end
@implementation Animal
@end

// コンポジションとしてAnimalクラスのオブジェクトをDuckに持たせる
@interface Duck : RLMObject
@property Animal *animal;
@property NSString *name;
@end
@implementation Duck
@end

@interface Frog : RLMObject
@property Animal *animal;
@property NSDate *dateProp;
@end
@implementation Frog
@end

// 使い方
Duck *duck =  [[Duck alloc] initWithValue:@{@"animal" : @{@"age" : @(3)}, @"name" : @"Gustav" }];

コレクションクラスについて

RealmにはRealmオブジェクトの集合を扱うためのクラスが複数あります。それらを総称して”Realmコレクションクラス”と呼びます。

  1. RLMResultsクエリを用いて取得したオブジェクトを表します。
  2. RLMArray、モデルオブジェクトにて1対多の関係を表すためのクラスです。
  3. RLMLinkingObjects, モデルにおいて逆方向の関連を表すクラスです。
  4. RLMCollection、Realmコレクションクラスに共通のインターフェースを定義したプロトコルです。すべてのRealmコレクションクラスはこのプロトコルに準拠しています。

Realmコレクションクラスはそれぞれのクラス間で一貫した振る舞いを提供するために、RLMCollectionプロトコルに準拠しています。このプロトコルはNSFastEnumerationを継承しています。そのため、Foundationフレームワークのコレクションクラスと同じように扱うことができます。またクエリや並べ替え、集計関数などRealmコレクションクラスで共通のAPIはこのプロトコルで宣言されています。RLMObjectには、プロトコルで定義されたメソッドに加えて、追加や削除などコレクションを変更するためのメソッドが定義されています。

RLMCollectionプロトコルを使うと、Realmコレクションクラスをジェネリックに取り扱うコードが書けます。

@implementation MyObject
- (void)operateOnCollection:(id<RLMCollection>)collection {
  // RLMResultsまたはRLMArrayのどちらでも渡すことができます
  NSLog(@"operating on collection of %@s", collection.objectClassName);
}
@end

書き込み

Realmへのオブジェクトの追加、変更、削除は、トランザクションの内部で行う必要があります。

Realmモデルクラスは、通常のObjective‑Cオブジェクトと同じようにインスタンス化し、使うことができます。アンマネージド(Unmanaged、まだRealmに追加されてない)RLMObjectのインスタンスは、通常のObjective‑Cのオブジェクトと同様に振る舞います。

永続化されたRLMRealmRLMObjectRLMResultsまたはRLMObjectのインスタンスは、生成されたスレッド内でなければ利用することができません。 別のスレッドで利用されると例外が発生します。これはRealmがトランザクションを分離するための必要な仕様です。

永続化された{{ RLMRealm }}RLMObject{{ RLMResults }}または{{ RLMArray }}のインスタンスは、生成されたスレッド内でなければ利用することができません。 別のスレッドで利用されると例外が発生します。これはRealmがトランザクションを分離するための必要な仕様です。

スレッド間でデータを共有したり、アプリケーションの再起動時に以前のデータを利用するには、Realmにデータを保存しなければなりません。これらの操作は、トランザクションの中で行う必要があります。

トランザクションには、無視できないオーバーヘッドが発生しますので、できるだけトランザクションの数は最小限に抑えることが望ましいです。

トランザクションは 同期的 に行われます。非同期には実行されません。もしあるスレッドが書き込みを始めたら、他のスレッドが同じRealmに対して書き込みを実行しようとしても、最初のスレッドが書き込みを完了するまで待つことになります。そして書き込みトランザクションはbeginWrite()の前に常に自動的にデータを最新の状態に更新します。そのため、データの作成で競合が発生することはありません。

トランザクションはディスクI/Oを伴う操作などと同様に失敗する可能性があります。 -[RLMRealm transactionWithBlock:]-[RLMRealm commitWriteTransaction]メソッドは必要に応じてNSErrorパラメータを渡すことができ、ディスクの容量不足などで失敗した際のエラーからリカバリすることができます。簡単にするためにこのドキュメントやサンプルコードではエラー処理をしていませんが、実際のアプリケーションでは、エラーを処理して必要に応じてリカバリするべきです。

オブジェクトの生成

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]];
  1. Objective‑Cにおけるalloc-initや、Swiftにおける指定イニシャライザを使うのはオブジェクトを作るもっともわかりやすい方法です。 すべてのプロパティの値を、オブジェクトがRealmに追加される前にセットする必要があるので注意してください。
  2. 適切なキーと値の組み合わせを持つディクショナリからオブジェクトを生成することもできます。
  3. 最後の方法は配列からオブジェクトを作る方法です。配列の各要素は、生成するモデルのプロパティと同じ順序で並んでいる必要があります。

ネストしたオブジェクト

RLMObjectのサブクラス、またはRLMObjectのプロパティを持つモデルオブジェクトは、ネストした配列やディクショナリを使って再帰的にオブジェクトをセットすることができます。

そのとき、配列またはディクショナリを使って、下記のように簡単にそれぞれのオブジェクトを生成することができます:

// すでに存在するオブジェクトを渡す代わりに...
Person *person1 = [[Person alloc] initWithValue:@[@"Jane", @30, @[aDog, anotherDog]]];

// ...配列を使ってその場で値を渡すことができます
Person *person2 = [[Person alloc] initWithValue:@[@"Jane", @30, @[@[@"Buster", @5],
                                                                  @[@"Buddy", @6]]]];

この方法は、ネストした配列やディクショナリがどのような組み合わせであっても動作します。この場合のRLMArrayRLMObjectのオブジェクトだけを格納できることに注意してください。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アーキテクチャーを採用しているので、書き込み処理の最中でも読み込み処理をブロックすることはありません。同時に複数のスレッドから書き込みをするのでなければ、大きな単位でトランザクションを使いましょう。細かいトランザクションを使うよりこの特性を活かすことができます。トランザクションがコミットされると、すべての他のRealmインスタンスに通知され、自動的に最新の状態に更新されます。

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

オブジェクトの更新

Realmのオブジェクトを更新するにはいくつかの方法があります。それぞれの方法には、状況によって異なるトレードオフがあるので、状況に応じて適切な方法を選択してください:

プロパティへの代入

オブジェクトを更新するには、トランザクションの中でプロパティをセットします。

// トランザクションを開始して、オブジェクトを更新する
[realm beginWriteTransaction];
author.name = @"Thomas Pynchon";
[realm commitWriteTransaction];

プライマリキーを使ってオブジェクトを作成・更新する

モデルにプライマリキーを指定しているなら、-[RLMRealm addOrUpdateObject:]を使って、オブジェクトがすでに存在する場合は更新、存在しない場合は新しく追加というように、追加または更新を一度に行うことができます。

// 以前に保存したものと同じプライマリキーを持つ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に保存されていた場合は、引数に渡されたオブジェクトで既存のオブジェクトを更新します。 もしid = 1のBookオブジェクトがRealmに保存されていない場合は、新しいBookオブジェクトを作成し、Realmに追加されます。

オブジェクトのプロパティを部分的に更新するには、オブジェクトのサブセットを渡します。具体的には下記のように更新したいプロパティの値とプライマリキーだけが含まれたディクショナリを渡します。

// プライマリキーが`1`のBookオブジェクトがすでにあるとき、
[realm beginWriteTransaction];
[Book createOrUpdateInRealm:realm withValue:@{@"id": @1, @"price": @9000.0f}];
// タイトルはそのままで値段のプロパティだけを更新することができます。
[realm commitWriteTransaction];

この項で紹介されている〜OrUpdateで終わるメソッドは、モデルにプライマリキーが定義されてない場合には使えません。

Realmに同じプライマリキーを持つオブジェクトが保存されていない場合は、このメソッドを呼ぶたびに新しいオブジェクトが作られて保存されます。

値を更新しようとするときはNSNull の存在に注意してください。NSNullOptionalなプロパティに対しては正しい値とみなされます。もし、ディクショナリの値としてNSNullを渡した場合、オブジェクトやプロパティは空の値に更新されます。データが空にするつもりがないなら、更新したい値だけを渡していることを確認してください。

キー値コーディング

RLMObjectRLMResultRLMArray クラスはいずれも キー値コーディング(KVC)に対応しています。 実行時にアップデートするプロパティが決定する場合に使用すると便利です。

また、キー値コーディングをコレクションオブジェクトに対して使用すると、多数のオブジェクトをループしてインスタンス化することが避けられるので、一括の更新を非常に効率的に行うことができます。

RLMResults<Person *> *persons = [Person allObjects];
[[RLMRealm defaultRealm] transactionWithBlock:^{
  [[persons firstObject] setValue:@YES forKeyPath:@"isFirst"];
  // すべてのPersonオブジェクトのプロパティを"Earth"に更新します
  [persons setValue:@"Earth" forKeyPath:@"planet"];
}];

オブジェクトの削除

オブジェクトを削除するには、トランザクションの中で削除したいオブジェクトを-[RLMRealm deleteObject:]メソッドに渡します。

// 保存されている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から、関連のオブジェクトを次々とたどりながら取得することもできます。

クエリの実行は、結果のオブジェクト(RLMResults)が実際に使用されるまだ遅延されます。つまり、メソッドチェーンなどによって一時的にRLMResultsオブジェクトが作られても、それだけでは(RLMResultsは使用されていないので)その時点ではクエリは実行されません。

RLMResultsの要素のアクセスするなどして)実際にクエリが実行された後、あるいはNotificationブロックが追加されたときは、RLMResultsはRealmに変更があるたびにバックグラウンドスレッドでクエリを実行し、自動的に最新の状態にアップデートされます。

もっとも基本的なオブジェクトを取得する方法は+[RLMObject allObjects]メソッドを使うことです。

このメソッドは、デフォルトRealmに保存されているRLMObjectオブジェクトのうち、指定したクラスのすべてのインスタンスを含むRLMResultsを返します。

RLMResults<Dog *> *dogs = [Dog allObjects]; // デフォルトRealmから、すべてのDogオブジェクトを取得します

検索条件を指定する

普段からNSPredicateの扱いに慣れているなら、Realmにおけるオブジェクトの検索方法を知っているも同然です。

RLMObjectsRLMRealmRLMArrayRLMResultsはすべて特定のRLMObjectオブジェクトをNSPredicateを使って検索するためのメソッドをサポートしています。 NSArrayを検索するときと同じように、直接NSPredicateオブジェクトを渡す、条件部分だけを文字列で渡す、などの方法が利用できます。

下記の例は、[RLMObject objectsWhere:]メソッドを使って、Dogクラスのcolor = "tan"かつ、nameの値がBから始まるという条件に合致するDog クラスのインスタンスをデフォルトRealmから取得します。

// 文字列で検索条件を指定します
RLMResults<Dog *> *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構文をサポートしています。

  • 比較演算子はプロパティ名と定数に対して使用できます。左辺と右辺のうち少なくとも一方はプロパティ名でなければなりません。
  • 比較演算子として==<=<>=>!=BETWEENintlonglong longfloatdoubleNSDateに対して使用できます。

    (例)age == 45

  • 同一性の比較==!=

    (例)[Employee objectsWhere:@”company == %@”, company]

  • bool型のプロパティに対しては比較演算子として==!=が使用できます。
  • NSStringNSData型のプロパティに対しては、==!=BEGINSWITHCONTAINSENDSWITH演算子が使用できます。

    (例)name CONTAINS ‘Ja’

  • NSStringプロパティに対して、LIKE演算子が使用できます。?*がワイルドカードとして使用できます。?は任意の1文字にマッチします。*は任意の0以上の文字にマッチします。value LIKE '?bc*'というクエリは、”abcde”や”cbc”にマッチします。
  • 文字列に対して大文字小文字を無視して比較するには、name CONTAINS[c] ‘Ja’のようにします。大文字小文字として扱われるのはアルファベットの”A-Z”および”a-z”であることに注意してください。[c]はダイアクリティカルマークを無視する[d]と同時に用いることができます。
  • ダイアクリティカルマークを無視して検索するには name BEGINSWITH[d] ‘e’ のように[d]を使用します。このクエリは étoile にマッチします。[c]と同時に組み合わせて使用できます。
  • 論理演算子として“AND”“OR”“NOT”が使用できます。

    (例)name BEGINSWITH ‘J’ AND age >= 32

  • いずれかの条件と一致するかどうか: IN

    (例)name IN {‘Lisa’, ‘Spike’, ‘Hachi’}

  • nilとの比較: ==, !=

    (例)[Company objectsWhere:@"ceo == nil"]

    Realmではnilは「何もない」ことを意味するのではなく、特別な値として扱われます。そのため、一般のSQLと異なりnilnil自身と一致します。

  • いずれかの要素が条件と一致するかどうか: ANY

    (例)ANY student.age < 21

  • 集計関数@count@min@max@sumおよび@avgRLMObjectRLMResultsのプロパティに対してサポートされています。

    (例)[Company objectsWhere:@"[email protected] > 5"]

    上記の例はemployeesを5件以上持つCompanyオブジェクトを取得します。

  • サブクエリは限定的にサポートされていて、下記の制限があります:
    • サブクエリに適用できる集計関数は@countだけです。
    • SUBQUERY(…)[email protected]と比較できるのは定数だけです。
    • 相関サブクエリはサポートされていません。

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

並べ替え

RLMResultsは、1つ、あるいは複数のプロパティ、またはキーパスの値を使って並べ替えることができます。下記は、先ほどのDogオブジェクトを検索した結果を、nameプロパティのアルファベット順で並べ替える例です。

// color = 'tan'かつ名前が"B"から始まるDogオブジェクトを、名前の昇順で取得します
RLMResults<Dog *> *sortedDogs = [[Dog objectsWhere:@"color = 'tan' AND name BEGINSWITH 'B'"]
                                      sortedResultsUsingKeyPath:@"name" ascending:YES];

次のように、キーパスとして[1対1の関連(#to-one-relationships)]が持つプロパティを指定することもできます。

RLMResults<Person *> *dogOwners = [Person allObjects];
RLMResults<Person *> *ownersByDogAge = [dogOwners sortedResultsUsingKeyPath:@"dog.age" ascending:YES];

sortedResultsUsingKeyPath:sortedResultsUsingProperty:メソッドはチェーンすることを想定しておらず、複数のプロパティによる並べ替えはサポートしていません。メソッドをチェーンして呼び出した場合は、最後のに呼び出されたメソッドだけが有効です。複数のプロパティを用いて並べ替える場合は、sortedResultsUsingDescriptors:メソッドを利用し、複数のRLMSortDescriptorオブジェクトを渡します。

詳しくは、

をご覧ください。

クエリの実行結果(Results)の順序はソートしなければ保証されません。パフォーマンス上の都合により、オブジェクトの挿入順は保持されません。挿入順を保持するいくつかの方法は、こちらに記載しています

クエリの連鎖

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

RLMResults<Dog *> *tanDogs = [Dog objectsWhere:@"color = 'tan'"];
RLMResults<Dog *> *tanDogsWithBNames = [tanDogs objectsWhere:@"name BEGINSWITH 'B'"];

検索結果({{ RLMResults }})の自動更新(ライブアップデート)

{{ RLMResults }}は常に最新の状態に自動的に更新されます。このため、同じ検索条件なら繰り返し検索を実行して、結果を取得し直す必要はありません。{{ RLMResults }}は常に現在のスレッドにおける最新の状態(同じスレッドのトランザクション中の状態を含む)を反映します。唯一の例外は、for...inループを使用するときです。for...inループの間でオブジェクトが削除されたり、クエリの条件に合致しなくなるような変更がされた場合でも、ループ開始時点のすべてのオブジェクトを列挙します。

RLMResults<Dog *> *puppies = [Dog objectsInRealm:realm where:@"age < 2"];
puppies.count; // => 0

[realm transactionWithBlock:^{
  [Dog createInRealm:realm withValue:@{@"name": @"Fido", @"age": @1}];
}];

puppies.count; // => 1

この仕組みはすべての{{ RLMResults }}オブジェクトに(検索条件や、並べ替えの有無にかかわらず)適用されます。

{{ RLMResults }}が持つこの性質によって、Realmは効率的で高速な処理を実現しています。さらにアプリケーションのコードをシンプルかつリアクティブにすることを可能にします。例えば、クエリの検索結果を表示するビューコントローラの場合は、{{ RLMResults }}を保持して表示に使うことで、アクセスするたびに再検索することなく、常に最新のデータを表示することができます。

合わせてRealmの通知を利用するとRealmのデータに更新がありアプリケーションのUIを更新する必要があるということを知ることができます。その場合も、{{ RLMResults }}を取得し直す必要はありません。

この自動更新の機能のために、件数やインデックスの値が一定ではないということは重要です。そのため高速列挙(Fast Enumeration)を使って要素をループしている時だけは{{ RLMResults }}は自動更新されません。このことにより、検索結果をループ中に結果に影響のある変更を加えても、すべてのマッチするオブジェクトを正しく変更することができます。

[realm beginWriteTransaction];
for (Person *person in [Person objectsInRealm:realm where:@"age == 10"]) {
  person.age++;
}
[realm commitWriteTransaction];

同じことは{{ RLMResults }}に対してキー値コーディングを利用することでも可能です。

取得データの数を制限

Realm以外のほとんどのデータベースには検索結果を「ページネーション(ページング)」する仕組みが備わっています(例えばSQLiteの’LIMIT’句によるものなどです)。この仕組みはディスクの過剰な読み込み、あるいは一度に大量のデータをメモリに読み込むことを避けるために使われます。

Realmのクエリは遅延実行されるので、このような「ページネーション」の仕組みはまったく必要ありません。なぜなら、Realmはクエリの実行結果の要素に対して、実際にアクセスしたときだけオブジェクトを読み込むからです。

UIや実装の都合により、クエリの実行結果の一部分だけが必要だったとします。そのときは、単に{{ RLMResults }}オブジェクトを用いて、必要な要素にだけアクセスすれば良いのです。

// データを5件に制限したい場合は、
// 単に最初から5番目までのオブジェクトにアクセスします
RLMResults<Dog *> *dogs = [Dog allObjects];
for (NSInteger i = 0; i < 5; i++) {
  Dog *dog = dogs[i];
  // ...
}

Realmについて

Realm はRealm Mobile Databaseのコンテナを表すインスタンスです。Realmとは ローカルRealm同期されたRealm があります。同期されたRealmは透過的にRealm Object Serverと別のデバイス間でデータを同期します。アプリケーションからは同期されたRealmをローカルファイルのように扱いますが、そのデータは別のデバイスから更新される可能性があります。どちらのRealmでも同じように操作できますが、同期されたRealmを開くには認証済みのユーザーオブジェクト、およびアクセス権が必要なことが異なります。

Realmについてさらに詳しくはRealm Data Modelのセクションをご覧ください。

Realmを開く

Realmを開くには、Realmオブジェクトをインスタンス化するだけです。これまで見てきたように、次のようにします。

RLMRealm *realm = [RLMRealm defaultRealm];

[realm transactionWithBlock:^{
  [realm addObject:mydog];
}];

上記のコード例ではデフォルトRealmのインスタンスを生成しています。Realmの設定を変更するセクションで説明しているように、設定オブジェクトを使用して、Realmの挙動を変更できます。

  • ローカルRealmの保存先を変更する
  • 同期されたRealmを使用するための認証情報と接続先のサーバーを設定する
  • ローカルRealmのマイグレーション処理を記述する

Realmの設定を変更する

Realmファイルの保存場所などの、Realmに対する設定をカスタマイズするには、RLMRealmConfigurationオブジェクトを使います。

設定オブジェクトはRealmのインスタンスを取得する際に[RLMRealm realmWithConfiguration:config error:&err]メソッドに渡すこともできますし、[RLMRealmConfiguration setDefaultConfiguration:config]メソッドを利用して、デフォルトRealmの設定とすることもできます。

例えば、次のようなアプリケーションを考えます。Web APIによるログイン認証があり、複数のアカウントを切り替えることができるとします。

その場合、アカウントごとにデフォルトRealmの保存場所を設定することで、それぞれのアカウントで別のRealmファイルを使い分けることができます。

@implementation SomeClass
+ (void)setDefaultRealmForUser:(NSString *)username {
  RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];

  // 保存先のディレクトリはデフォルトのままで、ファイル名をユーザー名を使うように変更します
   config.fileURL = [[[config.fileURL URLByDeletingLastPathComponent]
                       URLByAppendingPathComponent:username]
                       URLByAppendingPathExtension:@"realm"];

  // ConfigurationオブジェクトをデフォルトRealmで使用するように設定します
  [RLMRealmConfiguration setDefaultConfiguration:config];
}
@end

保存先やスキーマ、スキーマバージョンの異なる複数のRealmファイルを同時に使用できます。

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];

// アプリケーションバンドルのパスを設定します
config.fileURL = [[NSBundle mainBundle] URLForResource:@"MyBundledData" withExtension:@"realm"];
// アプリケーションバンドルは書き込み不可なので、読み込み専用に設定します。
config.readOnly = YES;

// RealmをConfigurationオブジェクト使って作成します
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];

// バンドルの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フォルダに保存することが推奨されています。

デフォルトRealm

すでにお気づきだと思いますが、これまで[RLMRealm defaultRealm]メソッドを呼ぶことで、変数realmを初期化してきました。

このメソッドは、各アプリケーションのDocumentsフォルダ(iOSの場合)またはApplication Supportフォルダ(macOS)に作られた”default.realm”ファイルのRLMRealmオブジェクトを返します。

Realmが持つたくさんのメソッドには、Realmインスンタスを引数として受け取るものと、自動的にデフォルトRealmが使われるものの両方があります。例えば、[RLMObject allObjects]は、[RLMObject allObjectsInRealm:[RLMRealm defaultRealm]]と同じ意味になります。

デフォルトRealmを作成するコンストラクタとデフォルトRealmを使用するメソッドはエラーハンドリングが使えないことに注意してください。これらのメソッドを使う場合は失敗したときはクラッシュします。さらに詳しくはエラーハンドリングセクションをご覧ください。

同期されたRealmを開く

同期されたRealmを開くにはこれまでのスタンドアローンで動作するRealmを開く場合と同じように、RLMRealmConfigurationオブジェクトとRealmクラスのコンストラクタ、ファクトリーメソッドを使用します。

ただし、同期されたRealmを開く場合はRLMRealmConfigurationオブジェクトのsyncConfigurationプロパティに、RLMSyncConfigurationオブジェクトをセットする必要があります。RLMSyncConfigurationオブジェクトにはRLMSyncUserオブジェクトと同期されたRealmのRealm Object Server上の場所を示すRealm URLを指定します。Realm URLはチルダ(~)を含むことができ、各ユーザーのユニークIDを表します。Realm URL中のチルダは自動的にユーザーIDに置き換えられます。(例えば、realms://acme.example.com/~/widgetsというURLは各ユーザーごとのウィジェットをRealmファイルでそれぞれ同期することを示します。)

開こうとしているRealmに対して読み取り権限しか持っていない場合、「非同期にRealmを開く」セクションにて説明しているasyncOpen APIを _使わなければなりません_ 。読み取り権限しかないRealmをasyncOpen`を使わずに開こうとするとエラーになります。

注意: URLにはファイル拡張子”.realm”を含めてはいけません。上記の例では主要なファイル名は”widgets”となりますが、Realmは自動的にその名前を用いて関連するファイルを作成します。

同期されたRealmに対してはinMemoryIdentifierfileURLを指定することはできません。syncConfigurationプロパティをセットすると、その2つのプロパティは自動的にnilがセットされます。同期されたRealmはキャッシュとしてディスクにファイルを持ちますが、フレームワークによって完全に隠蔽されています。

下記の例は同期されたRealmをURLを指定して開く方法です。

RLMSyncUser *user = [RLMSyncUser currentUser];

// Create the configuration
NSURL *syncServerURL = [NSURL URLWithString: @"realm://localhost:9080/~/userRealm"];
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:user realmURL:syncServerURL];

// Open the remote Realm
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];
// Any changes made to this Realm will be synced across all devices!

Realm URLに含まれるチルダ(~)は自動的にユーザーを示すユニークIDに変換されます。 例えば、「ウィジェット」を提供するアプリがあるとして、各ユーザーのウィジェットをRealmを使って同期する場合、次のようなURL(realms://acme.example.com/~/widgets)を使います。あるユーザーのIDが5917268だとすると、実際のURLはrealms://acme.example.com/5917268/widgetsのように扱われます。また別のユーザーのIDが8581230のときはURLは自動的にrealms://acme.example.com/8581230/widgetsとして扱われます。

この仕組みは、ユーザーごとにそれぞれ別のRealmファイルとして扱いたい場合に、コード上でファイル名をユーザーごとに変えるようなコードが不要になり、非常に便利です。

非同期にRealmを開く

Realmファイルを開くことはマイグレーションコンパクションを伴う場合は時間がかかる場合があります。または同期されたRealmでは初回同期のダウンロードに時間がかかります。そのような場合にasyncOpenメソッドを利用すると、非同期にそのような時間のかかる処理を実行し、Realmが利用可能になった時点で任意のスレッド、またはディスパッチキューにてコールバックを受けることができます。asyncOpenメソッドは読み取り専用のRealmに対しても使えます。

例:

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 1;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  // potentially lengthy data migration
};
[RLMRealm asyncOpenWithConfiguration:config
                       callbackQueue:dispatch_get_main_queue()
                            callback:^(RLMRealm *realm, NSError *error) {
  if (realm) {
    // Realm successfully opened, with migration applied on background thread
  } else if (error) {
    // Handle error that occurred while opening the Realm
  }
}];

同期されたRealmではすべてのサーバー上のリモートコンテンツをバックグラウンドスレッドでダウンロードし、完了した時点でコールバックが呼ばれます。

例:

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.syncConfiguration = [[RLMSyncConfiguration alloc] initWithUser:user realmURL:realmURL];
[RLMRealm asyncOpenWithConfiguration:config
                       callbackQueue:dispatch_get_main_queue()
                            callback:^(RLMRealm *realm, NSError *error) {
  if (realm) {
    // Realm successfully opened, with all remote data available
  } else if (error) {
    // Handle error that occurred while opening or downloading the contents of the Realm
  }
}];

In-Memory Realm

通常は、Realmはディスクにデータを保存しますが、RLMRealmConfigurationfileURLを指定する代わりにinMemoryIdentifierを利用することで、データをメモリ上のみに保持するRealmオブジェクトを作ることができます。

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.inMemoryIdentifier = @"MyInMemoryRealm";
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];

In-memory Realmでは、データをディスクに保存しないのでアプリケーションを終了するとデータは消えてしまいます。それ以外のクエリ、リレーションシップ、スレッドセーフなどの機能はすべて通常のRealmと同様に利用することができます。

ディスクへの読み書きによるオーバーヘッドの無い、柔軟なデータアクセス機能が必要なときに有効です。

In-memory Realmはプロセス間通信などに利用するため、一時ディレクトリにいくつかのファイルを作成します。

メモリ不足によりOSがスワップを要求したとき以外は、何のデータも書き込まれません。

注意: スコープを外れ、In-Memory Realmインスタンスへの参照がすべて無くなると、そのRealm内に保存されている、すべてのデータは解放されます。

アプリケーションの起動中は、In-MemoryなRealmインスタンスへの強参照を常に保持しておく必要があります。

エラーハンドリング

一般的なディスクI/Oの処理と同様に、RLMRealmインスタンスの作成はリソースが不足している環境下では失敗する可能性があります。実際は、各スレッドにおいて最初にRealmインスタンスを作成しようとするときだけエラーが起こる可能性があります。それ以降のアクセスではスレッドごとにキャッシュされたインスタンスが返されるので、失敗することはありません。

エラーを処理してリカバリするには、NSErrorポインタをerrorパラメータに渡します。

NSError *error = nil;

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (!realm) {
  // handle error
}

Realm間のオブジェクトのコピー

Realmに保存されたオブジェクトを他のRealmへコピーするには、+[RLMObject createInRealm:withValue:]メソッドにコピー元のオブジェクトを引数として渡します。 例えば、[MyRLMObjectSubclass createInRealm:otherRealm withValue:originalObjectInstance]のように使います。

+[RLMObject createInRealm:withValue:]メソッドは自分自身をプロパティに持つような循環する構造を持つオブジェクトはサポートしていません。 自分自身を関連に持つようなオブジェクトをこのメソッドのパラメータとして渡さないように注意してください。

Realmファイルの探し方

アプリケーション内のRealmファイルの場所がわからないときは、このStackOverflowの回答を参考にしてください。

補助的に作成されるRealmの関連ファイルについて

Realmでは.realm拡張子を持つメインのRealmファイルとは別に、いくつかの内部的に使用する関連ファイルとディレクトリを自動的に作成します。

  • .realm.lock - Realmファイル開く際の競合を防ぐためのロックとして使われます。
  • .realm.management - プロセス間の競合を防ぐためのロックファイルを格納しています。
  • .realm.note - スレッド・プロセス間の通知に使用する名前付きパイプのファイルです。

これらのファイルは、.realm拡張子のRealmのデータファイルには何の影響も与えません。またこれらのファイルを(実行中を除いて)削除したり移動したりすることも問題ありません。

Realmに関する問題を報告する際には、これらの関連ファイルを.realm拡張子のRealmのデータファイルと一緒に添付してください。関連ファイルには問題を調査するときに役に立つ情報が含まれています。

初期データとしてRealmをアプリケーションにバンドルする

初回起動を素早くするためなどに、アプリケーションに初期データを組み込むことはよくあります。 下記はRealmを初期データとしてバンドルする例です:

  1. はじめに、初期データの入ったRealmを用意します。 リリースする時と同じデータモデルを定義し、データを入れます。Realmファイルは、クロスプラットフォームで利用可能ですので、データの作成はOS Xや、iOSシミュレータ上で行っても問題ありません。(JSONImportの例をご覧ください)
  2. 初期データを入れるコードの終わりに、Realmファイルをコピーをするメソッドを使用し、Realm ファイルのサイズを最適化してください(-[RLMRealm writeCopyToPath:error:]をご覧ください)。 このメソッドを使ってRealmファイルをコピーすると、Realmのファイルサイズを小さくでき、最終的にアプリケーションのサイズが軽くなるのでユーザが速くダウンロードできます。
  3. コピーされたRealmファイルを、Xcodeのプロジェクトナビゲーターにドラッグ&ドロップします。
  4. プロジェクト設定のBuild Phaseタブで、”Copy Bundle Resources”にRealmファイルを追加します。
  5. この時点で、追加したRealmファイルにアプリケーションからアクセスできるようになります。 [[NSBundle mainBundle] pathForResource:ofType:]メソッドを使って、ファイルパスを取得します。
  6. こうして、[RLMRealm realmWithPath:readOnly:error:]メソッドで読み込み専用のRealmオブジェクトを作ることもできますし、その初期データを含んだRealmに書き込みたい場合は、[[NSFileManager defaultManager] copyItemAtPath:toPath:error:]メソッドを使って、アプリケーションのDocumentsディレクトリにRealmファイルをコピーしてから、[RLMRealm realmWithPath:]を使って、Realmオブジェクトを作ります。

  7. 同梱したRealmデータファイルが固定のデータのみを含んでいて、変更する必要が無いのであれば、RLMRealmConfigurationreadOnly = 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 error:nil];

コンパクション

Realmのアーキテクチャではファイルサイズは常に大きくなり、減ることはありません。マルチスレッドのセクションにも書いてあるように、そうすることによって優れたパフォーマンスと並列実行時の安全性を獲得しています。

さらに、

さらに、ファイルサイズを拡張させるのはコストの高いシステムコールを使用するので、それが頻繁に呼ばれることを避けるためにRealmは実行時にファイルサイズを縮小することはしません。その代わり、空き領域は自動的に再利用されます。

しかし、それでもいくつかのケースにおいては、Realmファイルが無視できない大きさに増加することがありました。そのため、このバージョンではshouldCompactOnLaunchというブロックのプロパティをRealmの設定オプションに追加しました。指定するとRealmを最初に開く際に条件に応じてコンパクションを自動的に実行できるようになります。

例:

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.shouldCompactOnLaunch = ^BOOL(NSUInteger totalBytes, NSUInteger usedBytes){
  // totalBytes refers to the size of the file on disk in bytes (data + free space)
  // usedBytes refers to the number of bytes used by data in the file

  // Compact if the file is over 100MB in size and less than 50% 'used'
  NSUInteger oneHundredMB = 100 * 1024 * 1024;
  return (totalBytes > oneHundredMB) && (usedBytes / totalBytes) < 0.5;
};

NSError *error = nil;
// Realm is compacted on the first open if the configuration block conditions were met.
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (error) {
  // handle error compacting or opening Realm
}

具体的な実装は、一度Realmファイルのコンテンツをすべて読み込み、別の場所に最適化した状態で書き込みます。そのあと、元のファイルと置き換えるという動作になります。そのため、コンパクションはデータ容量によっては負荷のかかる処理になります。

そのため、コンパクションを実行するタイミングについてはアプリケーションに応じて慎重に決定することを推奨いたします。

別のプロセスからアクセスがあった場合は、条件を満たしていたとしてもコンパクションは実行されません。それはRealmファイルが別のプロセスからアクセスされている状態でコンパクションを安全に実行することはできないためです。

shouldCompactOnLaunchプロパティは同期されたRealmではサポートされません。コンパクションはトランザクションログを破棄してしまうためです。Realmの同期にはトランザクションログを保持する必要があるからです。

Realmファイルを削除するには

キャッシュを消去する、データをすべてリセットするなど、いくつかのケースにおいて、Realmファイルを完全にディスクから削除することが適していることがあります。

Realmは可能なかぎり必要になるまでメモリのコピーを抑えるように工夫されており、Realmが管理するすべてのオブジェクトはメモリマップされたディスク上の参照として存在します。ファイルを安全に削除するにはすべてのオブジェクトが解放されていなければなりません。ここでいうオブジェクトとはRLMArrayRLMResultsRLMThreadSafeReferenceそしてRLMRealm自身のすべてを指します。

つまり、通常はRealmファイルを削除するにはRealmを最初に開く前のアプリケーションの開始直後が理想的です。どうしてもRealmファイルを実行中に削除したい場合は、オートリリースプール用いてすべてのオブジェクトを解放する必要があります。

最後に、これは厳密には必須ではありませんが、完全にすべてのファイルを削除しようとするなら、補助的に作成されるRealmの関連ファイルも同様に削除しましょう。

@autoreleasepool {
  // all Realm usage here
}
NSFileManager *manager = [NSFileManager defaultManager];
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
NSArray<NSURL *> *realmFileURLs = @[
  config.fileURL,
  [config.fileURL URLByAppendingPathExtension:@"lock"],
  [config.fileURL URLByAppendingPathExtension:@"note"],
  [config.fileURL URLByAppendingPathExtension:@"management"]
];
for (NSURL *URL in realmFileURLs) {
  NSError *error = nil;
  [manager removeItemAtURL:URL error:&error];
  if (error) {
    // handle error
  }
}

Appのバックグラウンド更新でRealmにアクセスする場合

iOS 8以降では、アプリが作成したファイルはデバイスがロックされている間はNSFileProtectionAPIの機能により、自動的に暗号化されます。

もしデバイスのロック中にアプリがRealmにアクセスする場合は、デフォルトではNSFileProtection属性が暗号化するように設定されているため、open() failed: Operation not permittedというメッセージの例外が発生します。

この問題を解決するためには、Realmと補助的に作成されるRealmの関連ファイルすべてに対して、デバイスのロック中でもファイルにアクセスできるように、ファイル保護の属性を緩める必要があります。例えばNSFileProtectionCompleteUntilFirstUserAuthenticationなどを指定します。

もし、上記のようにしてiOSのファイル保護機能を使わないように設定するのであれば、データを保護するためにRealmの暗号化機能を利用することをおすすめします。

Realmの関連ファイルは遅れて作成されたり、途中で削除されることもあるので、ファイル保護の属性はRealmファイルを保存しているフォルダに対して設定することをおすすめします。そうすることで、Realmと関連ファイルすべてに同じファイル保護の属性を簡単に設定でき、ファイルが遅れて作成される問題にも対処できます。

RLMRealm *realm = [RLMRealm defaultRealm];

// Realmファイルを保存するフォルダのパスを取得します
NSString *folderPath = realm.configuration.fileURL.URLByDeletingLastPathComponent.path;

// Realmファイルを保存するフォルダに対してファイル保護を無効にします
[[NSFileManager defaultManager] setAttributes:@{NSFileProtectionKey: NSFileProtectionNone}
                                 ofItemAtPath:folderPath error:nil];

マルチスレッド

Realmの読み取りトランザクションの生存期間はRLMRealmインスタンスがメモリ上に生存している期間と強く結びついています。古いトランザクション履歴が残り続けてしまうことを避けるため、Realmの自動更新を有効にし、バックグラウンドスレッドからRealmのAPIを利用する際は明示的にオートリリースプールで囲うようにしてください。

このことに関する詳細は現バージョンにおける制限事項をご覧ください。

独立したスレッドでRealmを使っている限りは、Realmのオブジェクトはすべて一般的なオブジェクトと同じように扱え、並行処理やマルチスレッドについて気にする必要はありません。 Realmにアクセスするためにロックや排他処理を考える必要はありません(たとえRealmが他のスレッドから同時に更新されるとしても)。そして、データの変更が起こるのはトランザクションブロックで囲まれた範囲だけと考えられます。

Realmは並行処理を簡単に扱えるようにするために、それぞれのスレッドで常に一貫性を保ったデータを返します。数多くのスレッドからRealmを同時に操作したとしても、それぞれのスレッドごとにスナップショットのデータが返され、不整合な状態が見えることがありません。

1つだけ注意しなければならないことは、複数のスレッドをまたいで同じ Realmインスタンス を共有することはできないということです。もし、複数のスレッドで同じオブジェクトにアクセスする必要がある場合は、それぞれのスレッドでRealmインスタンスを取得する必要があります。(そうでなければデータが不整合に見える可能性があります。)

他のスレッドから更新されたデータを反映するには

メインスレッド(またはランループを持つサブスレッド)では、ランループが回るごとに自動的に他のスレッドから更新されたデータが反映されます。それ以外のタイミングでは、その時点のスナップショットのデータが返され、他のスレッドでデータが更新されているかどうかを気にすることなく、常に一貫性のあるデータが見えることになります。

それぞれのスレッドで最初にRealmファイルをオープンしたとき、Realmの状態は最後のコミットが成功した状態にあります。そしてそれは次の更新が反映されるまでそのままです。 RealmはautorefreshNOでない限りは、ランループが回るたびに自動的に最新のデータに更新されます。 もしスレッドがランループを持っていない(一般的なバックグラウンドスレッド)場合は、最新のデータを反映するために-[RLMRealm refresh]メソッドを自分で呼ぶ必要があります。

また、トランザクションがコミットされたときも最新のデータが反映されます(-[RLMRealm commitWriteTransaction])。

定期的に行われる最新データの反映が失敗すると、トランザクション履歴が”Pinned”になり、ディスク領域の再利用を妨げます。それはファイルサイズの肥大を招くことがあります。この現象について詳しくは現バージョンにおける制限事項をご覧ください。

スレッド間でオブジェクトを受け渡す

アンマネージド(Unmanaged、Realmに追加される前の)RLMObjectのインスタンスは、通常のObjective‑Cのオブジェクトと同様に振る舞います。スレッドをまたいでオブジェクトを渡しても問題ありません。

RLMRealmRLMResultsRLMObjectおよび、マネージド(Managed、Realmに追加された後の)RLMObjectのインスタンスは、生成されたスレッド内でなければ利用することができません。 別のスレッドで利用されると例外が発生します。これはRealmがトランザクションを分離するための必要な仕様です。

そのため、Realmでは安全にスレッド間でオブジェクトを受け渡すための仕組みを用意しています。

  1. RLMThreadSafeReferenceのイニシャライザにスレッドをまたげないオブジェクトを渡して初期化する。
  2. RLMThreadSafeReferenceオブジェクトを別のスレッドまたはキューに渡す。
  3. オブジェクトが保存されているRealmの-[RLMRealm resolveThreadSafeReference:]メソッドを呼び、スレッドセーフ参照(RLMThreadSafeReference)を解決し、本来のオブジェクトを取り出す。 取り出したオブジェクトは普通のRealmオブジェクトと同様に利用できます。

使用例:

Person *person = [Person new];
person.name = @"Jane";
[realm transactionWithBlock:^{
  [realm addObject:person];
}];
RLMThreadSafeReference *personRef = [RLMThreadSafeReference
  referenceWithThreadConfined:person];

dispatch_async(queue, ^{
  @autoreleasepool {
    RLMRealm *realm = [RLMRealm realmWithConfiguration:realm.configuration
                                                 error:nil];
    Person *person = [realm resolveThreadSafeReference:personRef];
    if (!person) {
      return; // person was deleted
    }
    [realm transactionWithBlock:^{
      person.name = @"Jane Doe";
    }];
  }
});

RLMThreadSafeReferenceオブジェクトが解決できるのは一度きりです。解決に失敗すると、RLMThreadSafeReferenceが生成されたときの古いバージョンのデータに固定されて返ってきます。固定されたバージョンはRealmインスタンスが解放されるまで固定されたままです。そのためRLMThreadSafeReferenceはできるだけ短い期間で使い終わるべきで。長い間保持することは推奨しません。

いくつかのプロパティとメソッドについては、別のスレッドからでもアクセス可能です。

  • RLMRealm: すべてのプロパティ、クラスメソッド、イニシャライザ
  • RLMObject: isInvalidatedobjectSchemarealm、クラスメソッド、イニシャライザ
  • RLMResults: objectClassNamerealm
  • RLMObject: invalidatedobjectClassNamerealm

複数スレッド間でRealmを使う

同じRealmファイルを複数のスレッドから使う場合は、各スレッドでRealmインスタンスをそれぞれ生成する必要があります。 同じ設定によって作られたRealmインスタンスは、ディスク上で同じものを指します。

複数のスレッド間で、Realmインスタンスを共有することはサポートされていません。同じRealmファイルにアクセスするRealmインスタンスは、すべて同じ設定(RLMRealmConfiguration)でなければなりません。

Realmは、大量のデータを追加するときには、一つのトランザクション中に複数の一括更新をすることにより、非常に効率よく動作します。

また、メインスレッドをブロックすることを避けるためGrand Central Dispatchを使い、トランザクションをバックグラウンドで実行することができます。

RLMRealmオブジェクトはスレッドセーフではないため、複数のスレッド間で共有することができません。それぞれのスレッド/ディスパッチキューで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"      : randomString,
                                @"birthdate" : randomDate}];
      }

      // トランザクションを込みとして、
      // 他のスレッドのRealmからデータを利用できるようにします。
      [realm commitWriteTransaction];
    }
  }
});

JSON

RealmはJSONを直接的にはサポートしていません。ですが、[NSJSONSerialization JSONObjectWithData:options:error:]の戻り値からRLMObjectを生成することができます。 言い換えると、KVC準拠のオブジェクトであれば、RLMObject保存、更新する際のメソッドに渡すことができ、オブジェクトを保存したり更新することができます。

// A Realm Object that represents a city
@interface City : RLMObject
@property NSString *name;
@property NSInteger cityId;
// other properties left out ...
@end
@implementation City
@end // None needed

NSData *data = [@"{\"name\": \"San Francisco\", \"cityId\": 123}" dataUsingEncoding: NSUTF8StringEncoding];
RLMRealm *realm = [RLMRealm defaultRealm];

// JSONを使ってオブジェクトを保存します
[realm transactionWithBlock:^{
  id json = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
  [City createOrUpdateInRealm:realm withValue:json];
}];

JSONがさらにネストしたオブジェクトや配列を含む場合、自動的に1対1か1対多の関連としてマッピングされます。詳しくはネストしたオブジェクトのセクションをご覧ください。

この方法によってRealmにオブジェクトを保存、更新する場合は、JSONのプロパティ名と型は、対象となるRLMObjectのプロパティと型に完全に一致している必要があります。例えば、

  • float型のプロパティはfloat型を格納するNSNumber型である必要があります。
  • NSDate型やNSData型のプロパティは文字列から自動的に変換されません。[RLMObject createOrUpdateInRealm:withValue:]メソッドに渡す前に変換しておく必要があります。
  • 必須のプロパティにJSONからnull(例: NSNull)が渡された場合は例外が発生します。
  • 必須のプロパティに値が渡されなかった場合は例外が発生します。
  • JSON側にRLMObjectに定義されていないプロパティがあった場合は無視します。

もし利用しているJSONスキーマが完全にRealmオブジェクトの定義と一致していない場合は、サードパーティ製のマッピングライブラリをJSONの変換に使用することを推奨します。Objective‑Cには多数の積極的にメンテナンスされているマッピングライブラリがあり、Realmと一緒に使用できます。こちらのIssueにいくつか一覧にまとめてあります。

通知

addNotificationBlockメソッドでブロックを登録すると、RLMRealmRLMResultsRLMArrayまたはRLMLinkingObjectsオブジェクトが更新されたとき、通知を受けることができます。

個別のRLMObjectの変更を監視するにはキー値監視(KVO)が利用できます。

通知が即座に送られずに、複数のトランザクションの変更が1つの通知にまとめられることがあります。

戻り値の“Notificaiton Token”が保持されている限り、通知は有効です。 更新の通知を登録したクラスの“Notificaiton Token”の強参照を保持しておく必要があります。 “Notificaiton Token”が解放されると、登録された通知は自動的に解除されます。

Realmに対する通知

Realmインスタンスは、他のスレッドでトランザクションが完了するたびに、他のRealmインスタンスに対して通知を送ります:

// Realmの通知を監視するように登録します
token = [realm addNotificationBlock:^(NSString *notification, RLMRealm * realm) {
  [myViewController updateUI];
}];

// 通知が不要になったら
[token stop];

コレクションに対する通知

コレクションに対する通知はRealmに対する通知とは少し異なります。コレクションに対する通知では、この変更で実際に何が変わったのかがインデックスの配列を用いて通知されます。インデックスの配列には、1つ前に通知を受けたときから、追加、削除、および変更されたオブジェクトのインデックスが格納されています。

コレクションに対する通知は、非同期で通知されます。初回の通知はクエリが実行完了された時点で呼ばれます。そのあとは、コレクションに格納されているオブジェクトに変更、または追加が発生するトランザクションがコミットされるたびに通知されます。

変更内容は通知ブロックに渡されるRLMCollectionChangeパラメータを用いて知ることができます。このオブジェクトは変更があったインデックスの情報をdeletionsinsertionsmodificationsという変数で保持しています。

最初の2つ、deletionsinsertions変数はコレクションに対してオブジェクトを追加・削除するたびに変更された部分のインデックスを記録しています。 RLMResultsの場合は、検索条件に関する値が変更されて、検索条件に新しくマッチするオブジェクトが増えたとき、あるいはマッチしなくなってオブジェクトが減ったときに変更として扱われます。

RLMResultsの派生クラスを含むRLMArrayまたはRLMLinkingObjectsといったコレクションについては、さらに関連にオブジェクトが追加されたときと、関連からオブジェクトが削除されたときにも変更として扱われます。

modificationsはオブジェクトのプロパティが変更されたときにインデックスが含まれます。それは1対11対多の関連が変更された場合も当てはまります。ただし、逆方向の関連の変更の場合は含まれません。

@interface Dog : RLMObject
@property NSString *name;
@property NSData   *picture;
@property NSInteger age;
@end
@implementation Dog
@end
RLM_ARRAY_TYPE(Dog)
@interface Person : RLMObject
@property NSString             *name;
@property RLMArray<Dog *><Dog> *dogs;
@end
@implementation Person
@end

上記のようなモデルクラスが定義されているとき、dogsの所有者であるPersonオブジェクトの変更を監視するとします。その場合は、検索条件にマッチするPersonオブジェクトに下記の変更が加えられた場合に、通知が届きます。 * Personオブジェクトのnameプロパティを変更したとき * Personオブジェクトのdogsプロパティに要素を追加または削除したとき * Personオブジェクトと関連づけがあるDogオブジェクトのageプロパティを変更したとき

このようなきめ細やかな通知の仕組みにより、変更があった際にただすべてを再読み込みするだけでなく、より分かりやすいアニメーションや表示を伴ったUIの更新を行うことができます。

- (void)viewDidLoad {
  [super viewDidLoad];

  // RLMResultsの通知を監視します
  __weak typeof(self) weakSelf = self;
  self.notificationToken = [[Person objectsWhere:@"age > 5"] addNotificationBlock:^(RLMResults<Person *> *results, RLMCollectionChange *changes, NSError *error) {
    if (error) {
      NSLog(@"Failed to open Realm on background worker: %@", error);
      return;
    }

    UITableView *tableView = weakSelf.tableView;
    // 初回の通知ではchangeパラメータはnilになります
    if (!changes) {
      [tableView reloadData];
      return;
    }

    // Resultsに変更があったので、UITableViewに変更を適用します
    [tableView beginUpdates];
    [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0]
                     withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0]
                     withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0]
                     withRowAnimation:UITableViewRowAnimationAutomatic];
    [tableView endUpdates];
  }];
}
- (void)dealloc {
  [self.notificationToken stop];
}

オブジェクトに対する通知

Realmはオブジェクトレベルの変更に対する通知をサポートしています。特定のRealmオブジェクトの各プロパティの変更や、そのオブジェクトが削除されたことを通知を通じて知ることができます。

Realmに保存されているオブジェクトに対してのみ通知ブロックを設定できます。

通知ブロックは別のスレッドまたはプロセスから実行されたトランザクションの変更については、オブジェクトが(自動的に)更新される際に呼び出され、同じスレッドで発生したトランザクションについてはその後のある時点で呼び出されます。

通知ブロックは3つのパラメータを受け取ります。1つ目はdeletedBOOL型の値です。オブジェクトが削除されたことを示します。この値がYESの場合は他のパラメータはすべてnilとなり、通知ブロックはその後呼び出されることはありません。

2番目のパラメータはchangesRLMPropertyChangeを要素に持つNSArray型の値です。それぞれの要素はどのプロパティが変更されたのかを示すプロパティ名(文字列)と変更前の値、変更後の値を持ちます。

3番目のパラメータはNSErrorです。オブジェクトの変更に関してエラーが発生した場合はこの値が渡されます。そのとき、changesnildeletedNOになり、ブロックはその後呼び出されません。

@interface RLMStepCounter : RLMObject
@property NSInteger steps;
@end

@implementation RLMStepCounter
@end

RLMStepCounter *counter = [[RLMStepCounter alloc] init];
counter.steps = 0;
RLMRealm *realm = [RLMRealm defaultRealm];
[realm beginWriteTransaction];
[realm addObject:counter];
[realm commitWriteTransaction];

__block RLMNotificationToken *token = [counter addNotificationBlock:^(BOOL deleted,
                                                                      NSArray<RLMPropertyChange *> *changes,
                                                                      NSError *error) {
    if (deleted) {
        NSLog(@"The object was deleted.");
    } else if (error) {
        NSLog(@"An error occurred: %@", error);
    } else {
        for (RLMPropertyChange *property in changes) {
            if ([property.name isEqualToString:@"steps"] && [property.value integerValue] > 1000) {
                NSLog(@"Congratulations, you've exceeded 1000 steps.");
                [token stop];
                token = nil;
            }
        }

    }
}];

UI駆動の書き込み

Realmの通知は常に非同期で配信されます。アプリが遅くなったり、メインスレッドをブロックすることはありません。 しかし、メインスレッド上で同期的に変更を適用し、即座にUIに反映しなければならない状況も存在します。 そのような変更を含むトランザクションを「UI駆動の書き込み」と呼んでいます。

例えば、ユーザーがテーブルビューに項目を追加するとします。UIはアニメーションを伴いつつ、ユーザーが操作をしたらすぐに反映されるのが理想です。

しかし、そのあとにデータ追加による通知が配信されてしまうと、すでにデータソースにはデータが追加されていて、UIにも反映されているにもかかわらず、通知により再び新しい行を追加しようとします。このように二重に追加しようとしてしまうことで、UIとデータソースの間に矛盾した状態を引き起こし、アプリをクラッシュさせてしまいます。💥NSInternalInconsistencyExceptionが起こります。💥

「UI駆動の書き込み」の際にはNotificaiton Tokenを-[RLMRealm commitWriteTransactionWithoutNotifying:error:]Realm.commitWrite(withoutNotifying:)メソッドに渡すことで、通知を発生しないようにできます。

この機能は特に同期を有効にしたRealmと、コレクションに対する通知を使用している場合に有効です。「インターフェース駆動型の書き込み」についての回避策はアプリが完全に状態をコントロールできることを前提にしているので、同期が有効なRealmでは、変更が同期されるたびに通知が発生するためタイミングを完全にコントロールできないからです。

// Observe RLMResults Notifications
__weak typeof(self) weakSelf = self;
self.notificationToken = [self.collection addNotificationBlock:^(RLMResults<Item *> *results, RLMCollectionChange *changes, NSError *error) {
  if (error) {
    NSLog(@"Failed to open Realm on background worker: %@", error);
    return;
  }

  UITableView *tableView = weakSelf.tableView;
  // Initial run of the query will pass nil for the change information
  if (!changes) {
    [tableView reloadData];
    return;
  }

  // Query results have changed, so apply them to the UITableView
  [tableView beginUpdates];
  [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  [tableView endUpdates];
}];

- (void)insertItem {
  // Perform an interface-driven write on the main thread:
  [self.collection.realm beginWriteTransaction];
  [self.collection insertObject:[Item new] atIndex:0];
  // And mirror it instantly in the UI
  [tableView insertRowsAtIndexPaths:[NSIndexPath indexPathForRow:0 inSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  // Making sure the change notification doesn't apply the change a second time
  [self.collection.realm commitWriteTransactionWithoutNotifying:@[token]];
}

通知に関するAPI

通知に関して詳しくは、下記に示すAPIドキュメントをご覧ください:

キー値監視(KVO)

Realmオブジェクトのほとんどのプロパティはキー値監視(KVO)に対応しています

すべてのRLMObjectのサブクラスにおける永続化対象(保存しないプロパティではない)のプロパティはKVOに対応しています。永続化される前のinvalidatedRLMObject型とRLMArray型のプロパティも同様です。(RLMLinkingObjects型のプロパティはKVOには対応していません。)

Realmに追加される前(アンマネージド)のRLMObjectのサブクラスのオブジェクトに対するキー値監視は、一般的なNSObjectのサブクラスと同様に動作します。

しかし、監視対象のオブジェクトは、監視対象として登録されている間は[realm addObject:obj]などのメソッドでRealmに保存することができないので注意してください。

マネージドオブジェクト(Realmに追加された後のオブジェクト)に対するキー値監視は少し異なる動作をします。

マネージドオブジェクト(Realmに追加された後のオブジェクト)においては、そのプロパティの変わるタイミングが3種類あります。プロパティに値を直接代入したとき、[realm refresh]メソッドを呼び出したとき、または別のスレッドがトランザクションをコミットして自動的にRealmが更新されたとき、そして別のスレッドから変更があったがその前に[realm beginWriteTransaction]を呼び出してトランザクションを開始したために変更が通知されなかったときです。

直接代入する以外の2つのケースではすべての別スレッドから行われた変更は、一度に適用されます。そのため、KVOの通知は1回にまとめられます。途中の変更の状態は破棄されるので、1から10まで1つずつ数を増加させるプロパティがあった場合、10に変化する際の1つの通知だけを受け取ることになります。

トランザクションの外でプロパティの変更が行われる可能性があるので、-observeValueForKeyPath:ofObject:change:context:メソッドの中でマネージドRealmオブジェクトを変更することは推奨されません。

NSMutableArrayのプロパティとは異なり、RLMObjectのプロパティに対する変更を監視するには-mutableArrayValueForKey:を使う必要はありません(互換性のためにそのメソッドを利用しても同様に機能するようにはしていますが)。直接RLMObjectを変更するメソッドを呼び出すだけで(監視していれば)更新が通知されます。

サンプルコードに簡単なRealmとReactiveCocoa(Objective‑C)のサンプル、およびReactKit(Swift)のサンプルがありますので参考にしてください。

マイグレーション

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

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

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

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

ここでのポイントは、もし以前に前のデータモデルでのデータが保存されている場合、新しく定義し直したデータモデルとディスクに保存されている古いデータモデルとの間に不整合が生じてしまいます。 マイグレーションを実行せずにRealmを使おうとすると、例外が発生します。

注意することとして、初期値の設定はマイグレーションの最中(新しくオブジェクトを生成して追加したとき、クラスに新しくプロパティを追加したときなど)には適用されません。 この問題は不具合としてこちらのIssue #1793で管理されています。

マイグレーションを実行する

マイグレーションの定義

マイグレーション処理は、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オブジェクトが使用できます。

プロパティ名の変更

プロパティ名を変更した場合は、マイグレーション時にはプロパティ名の変更という操作を実行する方が、値や関連をコピーするよりも効率的に動作します。

マイグレーション時にプロパティを変更する場合は、変更後のプロパティ名がモデルに存在することと、変更前のプロパティ名がモデルに存在しないことを確認してください。

変更後のプロパティが変更前と異なるNULL可/不可属性、インデックスの設定を持つ場合は、名前の変更の際に適用されます。

以下は、PersonクラスのyearsSinceBirthプロパティをageに変更する例です。

// [AppDelegate didFinishLaunchingWithOptions:]の中に書きます

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.schemaVersion = 1;
config.migrationBlock = ^(RLMMigration *migration, uint64_t oldSchemaVersion) {
  // 初めてマイグレーションを実行するのでoldSchemaVersionは0です
  if (oldSchemaVersion < 1) {
    // 名前の変更は`enumerateObjects:`の外側で実行する必要があります。
    [migration renamePropertyForClass:Person.className oldName:@"yearsSinceBirth" newName:@"age"];
  }
};
[RLMRealmConfiguration setDefaultConfiguration:config];

バージョンの追加方法

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を代入することを推奨します。

暗号化

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.

Realmでは64バイトの暗号化キーを用いてAES-256とSHA-2暗号化方式でデータベースファイルを暗号化する機能を提供しています。

// ランダムな暗号化キーを生成します
NSMutableData *key = [NSMutableData dataWithLength:64];
SecRandomCopyBytes(kSecRandomDefault, key.length, (uint8_t *)key.mutableBytes);

// 暗号化されたRealmファイルを開きます
RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.encryptionKey = key;
NSError *error = nil;
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:&error];
if (!realm) {
    // もし暗号化キーが間違っている場合、`error`オブジェクトは"invalid database"を示します
    NSLog(@"Error opening realm: %@", error);
}

// 使い方は暗号化なしのRealmと変わりません
RLMResults<Dog *> *dogs = [Dog objectsInRealm:realm where:@"name contains 'Fido'"];

この機能を使用すると、ディスクに保存されるデータが透過的にAES-256で必要に応じて暗号/複合化され、SHA-2 HMACによって検証されます。

暗号化したRealmファイルのインスタンスを作成するには同じ暗号化キーが必要になります。

詳しくは暗号化のサンプルコードをご覧ください。暗号化キーの作り方、キーチェーンへの安全な保存方法、Realmへのキーの渡し方などが理解できます。

暗号化したRealmを使う場合、わずかにパフォーマンスが下がり(10%未満)ます。

サードパーティのクラッシュレポートツール(CrashlyticsやPLCrashReporterなど)は、暗号化した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
@implementation ClassBeingTested
+ (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:(NSJSONReadingOptions)nil error:nil];
  [realm transactionWithBlock:^{
    [User createOrUpdateInRealm:realm withValue:object];
  }];
}
@end

// Test Code
@implementation UnitTests
- (void)testThatUserIsUpdatedFromServer {
  RLMRealm *testRealm = [RLMRealm realmWithURL:kTestRealmURL];
  NSData *jsonData = [@"{\"email\": \"[email protected]\"}"
                      dataUsingEncoding:NSUTF8StringEncoding];
  [ClassBeingTested createOrUpdateUserInRealm:testRealm withData:jsonData];
  User *expectedUser = [User new];
  expectedUser.email = @"[email protected]";
  XCTAssertEqualObjects([User allObjectsInRealm:testRealm][0],
                        expectedUser,
                        @"User was not properly updated from server.");
}
@end

デバッグ

LLDBスクリプトとRealm Browserを使うと、Realmのデバッグをより簡単にできます。

最新のRealmのplugin/に同梱されている、LLDB拡張rlm_lldb.pyは、デバッグ時にRLMObjectRLMResultsRLMArrayオブジェクトを調べるのに非常に役に立ちます。LLDBの拡張を入れなければ、取得した結果のプロパティがnilや0と表示されます。

Screenshot of Xcode Debugging Console

注意: LLDBスクリプトは、Objective‑Cにのみ対応しています。Swiftのサポートは開発中です。

Realm.frameworkとテストターゲットをリンクしない

ダイナミックフレームワークとしてRealmを使用している場合、ユニットテストのターゲットがRealmフレームワークを見つけられるようにしておく必要があります。 そのためには、ユニットテストの”Framework Search Paths”にRealm.frameworkの親フォルダの場所を追加します。

テスト実行時に"Object type 'YourObject' is not managed by the Realm"というエラーが出る場合、Realm.frameworkがテストターゲットにリンクされていることが原因です。 問題を解決するために、テストターゲットからRealmのリンクを解除してください。

また、モデルクラスのファイルはアプリケーションのターゲットか、フレームワークのターゲットのどちらか一方にのみリンクされている必要があります。 そうなってなければ、テスト時にモデルクラスに重複が生じて問題が発生する可能性があります。 詳しくはこちらのIssueをご覧ください。

テスト対象のコードは、全てテストターゲットからアクセス可能である必要があります。(public修飾子を使うか、@testableを使います。)詳しくはこちらのStack Overflowの回答をご覧ください。

既知の制限事項

現在のバージョンの制限事項として下記に挙げる問題があります。

既知の問題について網羅的に知りたい場合はGitHub Issuesをご覧ください。

一般的な制限事項

Realmは、柔軟性とパフォーマンスのバランスをうまく保つため、保存するデータに対していくつかの制限事項があります。

  1. クラス名は57文字が上限です。
  2. プロパティ名は63文字が上限です。
  3. NSData型およびNSString型のプロパティは、16MB以上のデータを保存することはできません。 それ以上のデータを保存するには、16MB以下の複数のデータにに分割するか、ファイルとして保存し、Realmにはファイルパスを記録します。 16MB以上のデータを保存しようとすると、実行時に例外が投げられます。
  4. 各Realmファイルのサイズは、アプリケーションごとにに割り当てられるメモリサイズを超えてはいけません。割り当てられるメモリサイズは、デバイスごとに異なり、実行時のメモリの断片化にも依存します。(詳しくは、rdar://17119975 をご覧ください)それ以上のデータを保存される場合は、Realmファイルを複数に分割してください。
  5. 文字列による検索結果の並べ替え、および大文字小文字を無視する検索条件は、’基本ラテン文字’、’ラテン1補助’、’ラテン文字拡張A’、’ラテン文字拡張B’においてのみ動作します。(UTF-8の範囲は、0-591)

マルチスレッド

Realmファイルは複数のスレッドから同時にアクセスすることができますが、RealmやRealmオブジェクトのインスタンス、クエリ、Resultsオブジェクトをスレッドをまたいで受け渡すことはできません。スレッドをまたいでRealmオブジェクトを渡したい場合は、RLMThreadSafeReferenceを利用します。Realmのスレッドモデルについて詳しくはこちらをご覧ください。

モデルクラスのsetterおよびgetterメソッドはオーバーライドできません

Realmは、データベースのプロパティとデータベースの操作を連動させて、遅延ロードや高速な性能を実現するために、モデルクラスのsetterおよびgetterをオーバーライドしています。そのため、Realmモデルクラスではプロパティのsetterおよびgetterメソッドをオーバーライドすることはできません。

簡単な解決方法は保存しないプロパティとして宣言することです。保存しないプロパティのsetterおよびgetterメソッドは自由にオーバーライドすることが可能です。

ファイルサイズと中間データについて

Realmの読み取りトランザクションの生存期間はRLMRealmインスタンスがメモリ上に生存している期間と強く結びついています。古いトランザクション履歴が残り続けてしまうことを避けるため、Realmの自動更新を有効にし、バックグラウンドスレッドからRealmのAPIを利用する際は明示的にオートリリースプールで囲うようにしてください。

このことに関する詳細は現バージョンにおける制限事項をご覧ください。

SQLiteなどにデータを保存した時より、ディスクの使用容量が少なくなることを期待されることかと思います。 Realmファイルの容量が考えているよりも大きい場合は{{ RLMRealm }}は古い履歴データを残している可能性があります。

データの一貫性を保つために、Realmは最新のデータにアクセスしたときのみ履歴をアップデートします。 このことは、別のスレッドが多くのデータを長い時間をかけて書き込んでいる最中にデータを読み出そうとした場合、履歴はアップデートされずに古いデータを読み出すことになります。結果として、履歴の中間データが増加していくことになります。

この余分な領域は、最終的には再利用されるか消去されます。(強制的に空き領域を消去するにはshouldCompactOnLaunchを使用するかwriteCopyToPath:error:を使ってファイルをコピーします。そのとき、自動的にファイルサイズが最適化されます。)

この問題を避けるには、invalidateメソッドを呼び出し、Realmにこれまでに取得したデータはもう必要なくなったことを知らせてください。 そうすると、Realmは中間データの履歴を解放します。そして、次のアクセスのときに最新のデータを使うようにRealmが更新されます。

また、GCDを使ってRealmにアクセスしたときにも、この問題が発生する可能性があります。ブロックの実行が終了した後も、オートリリースプールのオブジェクトが解放されずに、{{ RLMRealm }}が解放されるまで古い履歴のデータが残ることによります。

この問題を避けるためにGCDのブロック内でRealmにアクセスするときは、明示的にオートリリースプールを利用してください。

オートインクリメント機能はサポートしていません

Realmは他の一般的なデータベースが備えている(スレッドセーフまたはプロセスセーフな)オートインクリメントなプライマリキーを生成する機構を備えていません。しかし、自動生成されるユニークキーが連番であることや、連続していること、数値であることが必須要件であることは、たいていの場合ありません。

たいていの場合は、ユニークな文字列をプライマリキーとすることでこと足ります。よくあるパターンはデフォルト値として[[NSUUID UUID] UUIDString] を使い、ユニークな文字列を生成します。

オートインクリメントが求められるケースとしては、挿入順を保持しておきたいという場合があります。このようなときは、オブジェクトの保持にRLMObjectを使うか、createdAtのようなプロパティをモデルに追加し、デフォルト値として[NSDate date]を使用しましょう(そしてcreatedAtでソートします)。

SwiftプロパティをRealmの値で初期化するには

Swiftのアプリケーションを書くときに、クラスやStructのプロパティをRealmの持つ値で初期化したいことがあります。例えば、下記のような場合です。

class SomeSwiftType {
  let persons = RLMPerson.allObjects(in: RLMRealm.default())
  // ...
}

このようなコードでプロパティを定義した場合は、Realmの設定が完了する前にこのコードが呼び出されてしまうことがあります。

例えば、マイグレーションブロックを設定する必要があり、applicationDidFinishLaunching()でそれを書いていたとしても、applicationDidFinishLaunching()が実行される前にこのコードが呼び出されてしまうことがあります。例えばStoryboardを使ってる場合などです。

この問題を避けるためには、

  1. Realmの設定が終わるまで、このようなクラス・Structの初期化を遅らせる。
  2. lazyキーワードを用いてプロパティを定義する。そうすると、プロパティのコードが実行されるのは最初にアクセスしたときになるので、Realmの設定の完了を待つことができます。
  3. プロパティの定義公文ではなく、独自に定義した初期化メソッドなどを指定してプロパティを設定する。

複数のプロセスから同時に暗号化されたRealmファイルにアクセスできない

この問題にはiOSのエクステンションが該当します。もしアプリとエクステンション、複数のプロセスからRealmファイルを共有する場合は、当面の間、問題を回避するには暗号化を解除して利用してください。 必要ならSecurityフレームワーク、CommonCryptoフレームワークの機能を利用して、個々のプロパティをNSData型にし、個別に暗号化を施す方法もあります。

Realmではこの問題をgithub Issuesの(#1693)と(#1845)でトラッキングしています。

-[NSPredicate evaluateWithObject:] rejects Realm collections as being non-collection objects

NSPredicateの内部実装による過度なチェックにより、NSPredicateのAPIのいくつかはRealmのコレクションと互換性がありません。 たとえば、-[NSPredicate evaluateWithObject:]はサブクエリを含む条件を渡すと例外を送出します。

Appleはこの問題を認識しています((rdar://31252694)。

アプリケーションでこれを回避する必要がある場合は、こちらのPull RequestでPR #4770提案されているコードを利用します。 NSPredicateを使用する前にRLMWorkaroundRadar31252694()を1度だけ実行します。

レシピ

実際のプロジェクトでよく求められる機能を、Realmを使って作成するためのレシピをまとめています。定期的に新しいレシピを追加する予定ですので、ときどきチェックしてください。追加してほしい例がありましたら、GitHubのIssueに書いてください。

同期

Realm Mobile PlatformはRealm Mobile Databaseを拡張し、ネットワークを通じてデバイス間の自動的なデータ同期を実現します。同期されたRealmに対応するために、いくつかの新しいクラスが導入されました。下記以降でRealm Mobile Databaseに新しく同期のために追加されたクラスを説明します。

Userクラス

Realmユーザー(RLMSyncUser)はRealm Object Serverの中心となるクラスです。サーバーと同期されたRealmファイルを開くには、まず認証済みのユーザーでログインする必要があります。RLMSyncUserクラスはユーザー名+パスワードによる認証に加え、SNSを使う方法などいくつかの認証方式をサポートしています。

ユーザーの作成、およびログインは次の2つの値が必要です。

  • 接続先のRealm ServerのURL
  • ユーザーを一意に識別するためのRLMSyncCredentialsオブジェクト(例: ユーザー名+パスワード、アクセストークンなど)

サーバーURL

次のように接続先のRealm Serverを示すNSURLオブジェクトを作成します。

NSURL *serverURL = [NSURL URLWithString:@"http://my.realmServer.com:9080"];

さらに詳しくは認証についてのドキュメントをご覧ください。

認証

ユーザーログインには認証が必要です。どのような認証プロバイダがサポートされているかについては、Realm Object Serverの認証についてのドキュメント をご覧ください。

ユーザー認証に使用する アカウント情報RLMSyncCredentialsオブジェクトによって表されます。このオブジェクトは次に示すさまざまな方法で作成できます。

  • ユーザー名/パスワードの組み合わせ。
  • 組み込みのサードパーティ認証サービスによって認証されたアクセストークン。
  • カスタム認証プロバイダによって認証されたトークン(カスタム認証をご覧ください)。

ユーザー名とパスワードによる認証はRealm Object Server内に完全に閉じています。ユーザーに関するコントロールをすべて自分自身で行うことができます。その他の認証サービスを利用する場合は、アプリケーション内で外部のサービスに接続し、認証トークンを取得する必要があります。

下記に数種類のアカウント情報の作成方法を示します。

ユーザー名/パスワード
RLMSyncCredentials *usernameCredentials = [RLMSyncCredentials credentialsWithUsername:@"username"
                                                                             password:@"password"
                                                                             register:NO];

ファクトリメソッドのregister引数によって新規にアカウントを作成するのか、既存のユーザーとしてログインするのかを区別します。すでに存在するユーザーに対してアカウントを作成しようとしたり、存在しないユーザーでログインしようとすると、エラーが返ります。

Google
RLMSyncCredentials *googleCredentials = [RLMSyncCredentials credentialsWithGoogleToken:@"Google token"];
Facebook
RLMSyncCredentials *facebookCredentials = [RLMSyncCredentials credentialsWithFacebookToken:@"Facebook token"];
Apple CloudKit
RLMSyncCredentials *cloudKitCredentials = [RLMSyncCredentials credentialsWithCloudKitToken:@"CloudKit token"];

カスタム認証

Realm Object Serverは上記以外の外部の認証サービスを利用することができます。このことにより、既存のシステムをそのままにRealm Object Serverと連携できます。カスタムの認証を使用する方法はObject Serverのカスタム認証のセクションをご覧ください。

RLMSyncCredentials *credentials = [[RLMSyncCredentials alloc] initWithCustomToken:@"custom token" provider:@"myauth" userInfo:nil];

3つ目の引数を用いて追加のログイン情報を渡すことができます。この仕組みはカスタムのログインメソッドを利用するときに使用します。詳しくはAPIリファレンスをご覧ください。

ユーザーを認証する

必要なパラメータが用意できれば、Realm Object Serverに接続し、同期されたRealmファイルを開くことができます。

[RLMSyncUser logInWithCredentials:usernameCredentials
                    authServerURL:serverURL
                     onCompletion:^(RLMSyncUser *user, NSError *error) {
  if (user) {
    // can now open a synchronized RLMRealm with this user
  } else if (error) {
    // handle error
  }
}];

Realm Mobile Platformでは1つのアプリケーション内で同時に複数のユーザーを利用することができます。

例えばメールのクライアントアプリでは複数のアカウントを切り替えて使用できます。そのような動作を実現するために、複数のユーザーを好きなときに同時に有効にできます。認証済みのユーザーオブジェクトをすべて取得するには+[RLMSyncUser all]クラスメソッドを利用します。

ログアウト

アプリケーションの利用者がアカウントからログアウトするには、-[RLMSyncUser logOut]メソッドを呼びます。そうすると、データ同期をサーバーに送信されていないデータをすべて送信し、Realm Object Serverとデータが完全に同期された後、次にアプリを起動したタイミングですべてのローカルデータはデバイスから削除されます。

ユーザーオブジェクトを使用する

currentUserメソッドは有効期限内のユーザーのうち最後にログインしたユーザーを返します。nilが返ってきたときは、現在有効なログイン済みのユーザーが存在しないことを示します。ログイン済みのユーザーが再度ログインしようとすると例外を返します。

RLMSyncUser *user = [RLMSyncUser currentUser];

複数のユーザーを扱っている場合は、allUsersメソッドを用いてログイン済みの全ユーザーのディクショナリオブジェクトを取得できます。

NSDictionary<NSString *, RLMSyncUser *> *allUsers = [RLMSyncUser allUsers];

ログイン済みユーザーが存在しない場合は、空のディクショナリが返されます。

ユーザーのパスワードを変更する

Realm Object Serverのユーザー名とパスワードで認証したユーザーのパスワードを変更するには-[RLMSyncUser changePassword:completion:]メソッドを使用します。

このAPIは非同期のネットワークリクエストをサーバーに送信します。レスポンスが返ってきたときにコールバックのブロックが呼び出されます。何らかのエラーが発生した場合はエラーオブジェクトがパラメータとして渡されます。処理が成功した場合はエラーオブジェクトはnilになります。

NSString *newPassword = @"swordfish";
[user changePassword:newPassword
          completion:^(NSError *error) {
  if (error) {
    // Something went wrong
  }
  // Otherwise, the password was successfully changed.
}];

管理者ユーザー

管理者ユーザーはRealm Object Serverにおける管理レベルのアクセス権限を、同じサーバー上のすべてRealmに対して持っています。ユーザーが 管理者 かどうかはRLMSyncUser.isAdminプロパティを使用して調べられます。このプロパティは最後にユーザーがログインした時の状態を反映しています。

[RLMSyncUser logInWithCredentials:usernameCredentials
                    authServerURL:serverURL
                     onCompletion:^(RLMSyncUser *user, NSError *error) {
  if (user) {
    // can now open a synchronized RLMRealm with this user
    // true if the user is an administrator on the ROS instance
    NSLog(@"User is admin: %@", @(user.isAdmin));
  } else if (error) {
    // handle error
  }
}];

同期セッション

同期されたRealmとRealm Object Serverとの接続はRLMSyncSessionオブジェクトとして表されます。 セッションオブジェクトは同期されたRealmを開いているユーザー(RLMSyncUser)オブジェクトから+[RLMSyncUser allSessions]メソッドや-[RLMSyncUser sessionForURL:]メソッドを用いて取得します。

基本的な使い方

セッションの状態を知るにはstateプロパティを使います。このプロパティからセッションがアクティブ、サーバーと未接続、またはエラー状態のいずれかであることがわかります。

セッションが正常なときはconfigurationプロパティはRLMSyncConfigurationプロパティを保持していて、同じRealmインスタンスを開くために(例えば別のスレッドで)利用できます。

同期の進捗を通知

セッションオブジェクトに 同期の進捗を通知するブロック を追加し、アプリの同期(アップロード、ダウンロード両方)の進捗状態を監視することができます。

同期の進捗を通知するブロックは同期システムによって継続的にランループが回るたびに登録したスレッド上で呼び出されます。もしスレッドにランループが存在しなければ、自動的に作成されます(つまりGCDキューから通知ブロックを登録できます)。複数の通知ブロックを同時に登録でき、ブロックはアップロードとダウンロードの両方について設定することができます。

ブロックの中では、全体のバイト数と、そのうちの転送済みのバイト数がパラメータとして渡されます(全体のバイト数は、転送済みのバイト数と未転送のバイト数として渡されます)。

ブロックが登録されると、トークンオブジェクトを返します。通知を停止するにはトークンオブジェクトに-stopメソッドを呼びます。ブロックの登録がすでに解除されている場合はこのメソッドは何もしません。 通知ブロックを登録した際に、通知ブロックが呼び出されないことがわかっている場合は、トークンオブジェクトではなくnilが返されます(例えばセッションがエラー状態になっている場合や、未同期のデータがない場合などです)。

通知ブロックは2つの種類があります。 継続的に 通知されるブロックでは通知を停止しない限りは、データの転送が行われるたびに何度でも継続して通知ブロックが呼び出されます。この種類の通知ブロックは例えばネットワークのインジケータを表示するために使用できます。下記の例では通信が発生するとインジケータを表示し、アップロードとダウンロードで色を変えて区別しています。

RLMSyncSession *session = [[RLMSyncUser currentUser] sessionForURL:realmURL];
void(^workBlock)(NSUInteger, NSUInteger) = ^(NSUInteger downloaded, NSUInteger downloadable) {
  if ((double)downloaded/(double)downloadable >= 1) {
    [viewController hideActivityIndicator];
  } else {
    [viewController showActivityIndicator];
  }
};
RLMProgressNotificationToken *token;
token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionDownload
                                                mode:RLMSyncProgressReportIndefinitely
                                               block:workBlock];

// Much later...
[token stop];

もう一つに、 ワンショットで通知が終了する ブロックがあります。このブロックは対象の同期タスクが終わるまで通知し、対象のタスクが完了すると、自動的に通知ブロックを解除します。この種類のブロックは例えば初回のRealmファイルの同期が完了するまでの進捗状況の表示や、大きめのデータのアップロードの進捗表示に利用できます。

RLMSyncSession *session = [[RLMSyncUser currentUser] sessionForURL:realmURL];
RLMProgressNotificationToken *token;
void(^workBlock)(NSUInteger, NSUInteger) = ^(NSUInteger uploaded, NSUInteger uploadable) {
  double progress = ((double)uploaded/(double)uploadable);
  progress = progress > 1 ? 1 : progress;
  [viewController updateProgressBarWithFraction:progress];
  if (progress == 1) {
    [viewController hideProgressBar];
    [token stop];
  }
};
token = [session addProgressNotificationForDirection:RLMSyncProgressDirectionUpload
                                                mode:RLMSyncProgressForCurrentlyOutstandingWork
                                               block:workBlock];

アクセスコントロール

Realm Mobile Platformでは、どのユーザーが同期されたRealmにアクセスできるのかを柔軟にコントロールできる仕組みがあります。 例えば、1つのRealmを複数のユーザーが共同で編集するようなアプリを作ることもできます。 また、あるユーザーのみが編集できるファイルを公開し、他のユーザーは読み取り専用で閲覧だけを可能にするといったことも可能です。

Realmのアクセス権限には3種類のレベルが存在します。

  • mayReadは読み取り権限が与えられていることを示します。
  • mayWriteは書き込み権限が与えられていることを示します。
  • mayManageはそのRealmに対するアクセス権限を変更できることを示します。

アクセス権限を明示的に与えなかった場合は、デフォルトの動作ではRealmファイルの作成者だけがそのファイルにアクセスできます。 唯一の例外はAdminユーザーです。Adminユーザーは常にすべてのRealmファイルに対してすべてのアクセス権限を持ちます。

書き込み権限のみという状態(例: mayReadをセットせずにmayWriteだけをセットした場合など)は現在サポートしていません。

アクセス権限の概念についてより詳しく知るには、Realm Object Serverドキュメントのアクセスコントロールセクションをご覧ください。

アクセス権限の状態を取得する

特定のユーザーがアクセス可能なすべてのRealmに対するアクセス権限の状態を調べるには-[RLMSyncUser retrievePermissionsWithCallback:]メソッドを使用します。

[[RLMSyncUser currentUser] retrievePermissionsWithCallback:^(RLMSyncPermissionResults *permissions, NSError *error) {
  if (error) {
    // handle error
    return;
  }
  // success! access permissions
}];

アクセス権限を変更する

アクセス権の設定を変更するということは特定のアクセス権限の設定を直接付与または取り消す方法と、パーミションの許可を申請、承認する2つの方法があります。

アクセス権を付与する

Permission Valueを利用して他のユーザーにアクセス権限の状態を適用(付与)し、Realmへのアクセス権を直接変更できます。

RLMSyncPermissionValue *permission = [[RLMSyncPermissionValue alloc]
                                       initWithRealmPath:realmPath                 // The remote Realm path on which to apply the changes
                                                  userID:anotherUserID             // The user ID for which these permission changes should be applied
                                             accessLevel:RLMSyncAccessLevelWrite]; // The access level to be granted
[user applyPermission:permission callback:^(NSError *error) {
  if (error) {
    // handle error
    return;
  }
  // permission was successfully applied
}];

アクセス権限の変更をユーザーが管理しているすべてのRealmに適用するにはrealmPath*を指定します。 Object Serverが管理するすべてのユーザーに同じアクセス権限を適用するにはuserID*を指定します。

Realm Object ServerのユーザーIDを使ってアクセス権を付与する方法に加えて、Realm Object Serverのユーザー名を使ってアクセス権を付与することもできます。

RLMSyncPermissionValue *permission = [[RLMSyncPermissionValue alloc]
                                       initWithRealmPath:realmPath
                                                username:@"[email protected]"
                                             accessLevel:RLMSyncAccessLevelWrite];
[user applyPermission:permission callback:^(NSError *error) {
  // ...
}];
アクセス権限を取り消す

アクセス権限を取り消すを取り消すには、RLMSyncAccessLevelNoneのアクセス権を新たに設定するか、-[RLMSyncUser revokePermission:callback:]メソッドを使用します。2つの方法は完全に等価です。

PermissionOffer/Response

RLMSyncPermissionOfferRLMSyncPermissionOfferResponseを用いてユーザー間でRealmを共有できます。すべてはクライアント側のコードだけで実現できます。サーバーサイドのコードを書く必要は一切ありません。

PermissionOfferオブジェクトはManagement Realmに書き込むことで(同期されることによって)サーバー側に作成され、処理されます。Management Realmは通常の同期されたRealmと同じように書き込んだり読み取りが可能です。ただし、Realm Object ServerはManagement Realmを特別に監視するように作られています。例えばPermission ChangeオブジェクトをManagement Realmに保存することはRealmファイルのアクセス権を変更することを意味します。

特定のユーザーのManagement Realmを取得するには-[RLMSyncUser managementRealmWithError:] メソッドを使用します。

Realmの共有は次の手順で行います。

  1. RLMSyncPermissionOfferオブジェクトを作成し共有するRealmファイルのユーザーのマネジメントRealmに保存します。
  2. RLMSyncPermissionOfferオブジェクトがサーバーに同期され、Realm Object Serverによって処理されるのを待ちます。完了するとサーバーによりtokenプロパティに値がセットされます。
  3. トークンを共有したい相手のユーザーに送ります。
  4. トークンを受け取ったユーザーはRLMSyncPermissionOfferResponseオブジェクトを作成してマネジメントRealmに保存します。
  5. RLMSyncPermissionOfferResponseオブジェクトがサーバーに同期され、ealm Object Serverによって処理されるのを待ちます。完了するとサーバーによりrealmUrlプロパティに値がセットされます。
  6. そうすると共有先のユーザーはRealmファイルにアクセスできるようになります。
RLMSyncPermissionOffer *shareOffer = [RLMSyncPermissionOffer permissionOfferWithRealmURL:realmURL
                                                                               expiresAt:nil
                                                                                    read:YES
                                                                                   write:YES
                                                                                  manage:YES];

// Add to management Realm to sync with ROS
[managementRealm transactionWithBlock:^{
  [managementRealm addObject:shareOffer];
}];

// Wait for server to process
RLMNotificationToken *shareOfferNotificationToken = [shareOffer addNotificationBlock:^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) {
  if (deleted == NO && error == nil && shareOffer.status == RLMSyncManagementObjectStatusSuccess && shareOffer.token) {
    // Send `token` to the other user
  }
}];

Permission Valueモデルオブジェクトと同じように、アクセス権はreadwritemanageの3種類をrealmURLに対して指定できます。 expiresAtはトークンの有効期限を示します。expiresAtを指定しない、もしくはnilを渡した場合は、有効期限は無期限になります。有効期限とはトークンの使用期限であり、すでにトークンを使用したユーザーのアクセス権が無くなるわけではありません。

別のユーザーも同じトークンを使ってRLMSyncPermissionOfferResponseを取得できます。

// Create response with received token
RLMSyncPermissionOfferResponse *response = [RLMSyncPermissionOfferResponse permissionOfferResponseWithToken:token];

// Add to management Realm to sync with ROS
[managementRealm transactionWithBlock:^{
  [managementRealm addObject:response];
}];

// Wait for server to process
RLMNotificationToken *acceptShareNotificationToken = [response addNotificationBlock:^(BOOL deleted, NSArray<RLMPropertyChange *> *changes, NSError *error) {
  if (deleted == NO && error == nil && response.status == RLMSyncManagementObjectStatusSuccess && response.realmUrl) {
    // User can now access Realm at `response.realmUrl`
  }
}];

RLMSyncPermissionOfferによるアクセス権限の変更は既存の状態に追加する形で行われます。つまりすでにwrite権限のあるユーザーがread権限を取得したとしてもwrite権限が失われることはありません。

共有はRLMSyncPermissionOfferオブジェクトをマネジメントRealmから削除することで中止できます。またはexpiresAtプロパティを過去に設定します。しかし新しくそのファイルを共有しようとするユーザーは受け付けなくなりますが、すでにアクセス権を獲得したユーザーのアクセス権が無くなるわけでは ありません

ログ出力

同期システムは数種類のログ出力レベルをサポートしています。下記のようにRLMSyncManager シングルトンオブジェクトを通じて、ログレベルを調整できます。

[[RLMSyncManager sharedManager] setLogLevel:RLMSyncLogLevelOff];

ログ出力レベルは Realmファイルを開く前に設定しなければなりません 。Realmファイルを開いた後にログ出力レベルを変更したとしても効果はありません。

ログ出力レベルに関して詳しくは、Realm Object Serverの設定に関するドキュメントをご覧ください。

エラー処理

同期に関すAPIはさまざまな理由で失敗することがあります。コンプリーションブロックを持つAPIについては発生したエラーを取得できます。APIが失敗した場合はエラーオブジェクトがパラメータとして渡され、エラーの詳細を取得できます。

RLMSyncManagerのシングルトンオブジェクトにエラーハンドラをセットすることを 強く推奨します 。グローバル、または特定の同期セッションにて発生したエラーについてはこのエラーハンドラによって通知されます。エラーが発生したときはエラーハンドラが呼ばれ、エラーを示すオブジェクトを渡します。必要に応じてエラーが発生したセッションを示すRLMSyncSessionオブジェクトも同時に渡します。

[[RLMSyncManager sharedManager] setErrorHandler:^(NSError *error, RLMSyncSession *session) {
  // handle error
}];

エラーの種類について

Realm Mobile Platformで発生したエラーはRLMSyncErrorDomainを持つNSErrorオブジェクトとして渡されます。エラーコードについて、RLMSyncErrorRLMSyncAuthErrorの定義を参照してください。

クライアントリセット

Realm Object Serverがクラッシュしてバックアップから復元しなければならなくなったときは、アプリに対してクライアントリセットを要求することがあります。これはローカルのRealmがサーバーのRealmよりも新しくなってしまったときに起こります(例えば、Realm Object Serverのバックアップ後にアプリケーションから変更があった場合は、復元した際にその変更は失われるのでアプリケーションがサーバーより新しい状態になります)。

クライアントリセットは次のような処理を行います。ローカルのRealmファイルのバックアップを作成します。その後オリジナルのRealmファイルは削除されます。次にRealm Object Serverに接続したタイミングで、新しくRealmファイルをサーバーからダウンロードします。Realm Object Serverのバックアップ後に行われた変更についてはバックアップには残っていますが復元されません。

クライアントリセットが要求されたかどうかは、RLMSyncManagerオブジェクトのエラーハンドラにて通知されます。クライアントリセットのエラーはRLMSyncErrorClientResetError として表されます。

エラーオブジェクトに加えてさらに2つのオブジェクトが渡されます。クライアントリセットの処理中に作成されるバックアップコピーの保存先とクライアントリセットの処理を開始するために呼び出すブロックです。

ブロックを自分で呼び出してクライアントリセットの処理を開始するには、ブロックを呼び出す前にすべてのRealmインスタンスは 解放されていなければなりませんRLMRealmが完全にInvalidatedの状態であったとしてもオートリリースプールに参照を解放させるためにすべての参照についてnilを代入する必要があるかもしれません。そうすることでクライアントリセットのプロセスが完了した際にはすぐにRealmをオープンし、同期を再開できます。

もしブロックを自分で呼ばなかった場合は、クライアントリセットの処理は次にアプリが起動したタイミングで自動的に行われます。最初に<%= terms [:RLMSyncManager]%>にアクセスしたときに行われます。オリジナルのバックアップからデータを移行する必要がある場合は、自分自身で行う必要があります。

クライアントリセットが必要なRealmについてはデータの読み込み、および書き込みは通常と同じようにすることができますが、 その変更はクライアントリセットが完了し、Realmを再ダウンロードしない限りはは同期されません 。この動作について知っておくことは非常に重要です。アプリケーションでは少なくともクライアントリセットのエラーを監視する必要があります。必要ならクライアントリセット後に行われた変更を反映するために処理をする必要があります。

クライアントリセットの処理に伴い作成されたバックアップファイルの場所はエラーオブジェクトの-[NSError rlmSync_clientResetBackedUpRealmPath]メソッドを使用して得られるオブジェクトからわかります。NSErrorオブジェクトをパラメータとして渡す必要があります。または、userInfoディクショナリのkRLMSyncPathOfRealmBackupCopyKeyキーの値からも同じ値を取得できます。

クライアントリセットを開始するブロックはNSErrorオブジェクトの-[NSError rlmSync_clientResetBlock]から取得できます。userInfoディクショナリのkRLMSyncInitiateClientResetBlockKeyキーの値からも同じブロックを取得できます。

下記の例はクライアントリセットをどのように扱うかを示しています。

[[RLMSyncManager sharedManager] setErrorHandler:^(NSError *error, RLMSyncSession *session) {
  if (error.code == RLMSyncErrorClientResetError) {
    [realmManager closeRealmSafely];
    [realmManager saveBackupRealmPath:[error rlmSync_clientResetBackedUpRealmPath]];
    [error rlmSync_clientResetBlock]();
    return;
  }
  // Handle other errors...
}];

Realm Object Serverがクライアントリセットをどう扱うかについての詳細はRealm Object Serverのドキュメントをご覧ください。

Permission Denied Error

ユーザーが適切なアクセス権を持たないRealmファイルを開こうとすると、Permission Denied Errorが発生します。このエラーは読み取り権限しか持たないRealmに書き込みをしようとしたり、まったくアクセス権を持たないRealmファイルを開こうとすると発生します。

読み取り権限しか持たない同期されたRealmを開くには、非同期のAPI(+[RLMRealm asyncOpenWithConfiguration:callbackQueue:callback:] )を使用しなければなりません。同期のAPIを使って開こうとするとPermission Denied Errorが起こることがあります。

Permission Denied ErrorはRLMSyncErrorPermissionDeniedError として表されます。エラーオブジェクトはパラメータを持たないブロックを含みます。 このブロックはエラーオブジェクトに対して-[NSError rlmSync_deleteRealmBlock]を呼ぶことで取得できます。userInfoに対してkRLMSyncInitiateDeleteRealmBlockKeyキーを用いて直接取得することもできます。

いずれの場合も、Permission Denied Errorは、ユーザーのデバイス上のRealmのローカルコピーが サーバーと同期できない状態になったことを示します。ファイルは自動的に次の起動時に削除されます。エラーに含まれるブロックを呼び出して直ちに削除することも可能です。

直ちにRealmファイルを削除するためにブロックを呼び出す場合は、すべてのRealmインスタンスが無効化され、解放されていなければなりません。自動解放プールが働くまではすべてのRLMRealmが無効化されておらず、Realmへの参照が存在していることがあります。正しいアクセス権がセットされると、すぐにRealmは同期を再開します。

同期されたRealmのマイグレーション

同期されたRealmについてはモデルクラスのマイグレーションは自動的に適用されます。ただし、いくつかの制限と注意事項があります。

  • 変更は追加によってのみ行われます。新しくクラスを追加する、新しくプロパティを追加する、という場合のみ自動的に変更が適用されます。
  • クラスからプロパティを削除した場合は、自動的にデータベースからその値が無くなるわけではありません。しかしRealmは削除されたプロパティを無視するので問題になりません。新しく作られたオブジェクトでもそのプロパティは存在しますが、Realmによってnilがセットされます。Null不可のプロパティの場合は数値型にはゼロが、文字列には空文字がセットされます。
  • カスタムのマイグレーションブロックは呼び出されません。セットすると例外が送出されます。
  • 破壊的な変更、例えばその変更に既存のRealmの情報に影響を与える場合など、は直接的にはサポートしていません。これはプロパティの型を名前はそのままで変更したり(またはNull不可の文字列型のプロパティをNull可に変更するなど)、プライマリーキーを変更したり、Null可をNull不可に変更したりなどを含みます。

同期されたRealmに対してカスタムのマイグレーション処理が必要な場合は、通知ブロック内で変更を適用します。サーバーサイドの場合はNode.js SDKを利用します。しかし、その変更が破壊的なものを含む場合は、Realm Object Serverとの同期はBad changeset receivedエラーによって中止されます。

破壊的な変更を行う場合は既存のファイルをマイグレーションするのではなく、 新しく 同期されたRealmを新しいデータモデルを使って作成し、既存のRealmファイルの変更を監視して新しいRealmにコピーするような処理を書きます。破壊的な変更を伴わない場合でもこの方法は利用することができます。

スタンドアローンのRealmを同期されたRealmに変換する

スタンドアローンのRealmファイルは自動的には同期されたRealmには変換されません。将来的にはそのような機能を追加することを予定しています。

ローカルのRealmを同期されたRealmに変換したい場合は、新しく同期されたRealmを作成し、手作業でローカルのRealmのデータをコピーしてください。

コンフリクトの解決

コンフリクトの解決については、Realm Object Serverのドキュメントをご覧ください。

管理者用クライアントツール

いくつかのAPIは管理者ユーザー(isAdminフラグがtrueであるユーザー)でなければ使用できません。

パスワードの変更

管理者ユーザーは-[RLMSyncUser changePassword:forUserID:completion:]を用いてユーザーのパスワードを変更できます。引数にはパスワードを変更したいユーザーのRealm Object ServerにおけるユーザーIDを渡します。

ユーザー情報の取得

管理者ユーザーは-[RLMSyncUser retrieveInfoForUser:identityProvider:completion:]を用いてRealm Object Serverに存在するユーザーの情報を取得できます。

最初の引数は認証プロバイダによって与えられたユーザーを識別するID文字列です。例えばユーザーがRealm Object Serverのユーザー名とパスワードの認証によって登録されているなら、この文字列はユーザー名になります。Realm Object ServerのユーザーIDと間違えないようにしてください。

2つ目の引数はユーザーが登録されている認証プロバイダの識別子です。

処理は非同期で実行され、コールバックで結果が返されます。ユーザーの検索に失敗した場合はコールバックブロックにエラーオブジェクトが渡されます(ユーザーが存在しない、実行しているユーザーが管理者ユーザーではない、など)。処理に成功した場合はRLMSyncUserInfoが渡され、ユーザーの情報が含まれています。

NSString *targetUserIdentity = @"[email protected]";
[adminUser retrieveInfoForUser:targetUserIdentity
              identityProvider:RLMIdentityProviderUsernamePassword
                    completion:^(RLMSyncUserInfo *userInfo, NSError *error) {
  if (error) {
    // An error occurred
  }
  // Examine userInfo for the user's information...
}];

FAQ

Realmのライブラリは、どのくらいの容量がありますか?

アプリケーションをリリース用にビルドすると、Realmライブラリによってアプリケーション自体の容量は5MBから8MB程度ダウンロードサイズが増加します。

現在、配布されているRealmのライブラリは、iOSとwatchOS、tvOSのシミュレータ用のバイナリやデバッグシンボル、そしてBitcodeを含むため著しく大きくなっています。それらはビルド時にXcodeによって自動的に取り除かれます。

Realmはオープンソースソフトウェアですか?

その通りです!RealmのC++で書かれた内部ストレージエンジンと各プログラミング言語のSDKは完全にオープンソースソフトウェアとして公開されています。ライセンスはApache 2.0です。

Realmはオプションとしてデータ同期のコンポーネントをクローズドソースで提供していますが、これはRealmを組み込みのデータベースとして利用するだけなら必須ではありません。

アプリケーションを起動したときにMixpanelへの通信が発生しているのを確認しましたが、なぜでしょうか?

Realmは匿名の統計データを収集しています。統計データの送信は、デバッガに接続されているか、シミュレータ上で動作している時のみ行われます。送信するデータに個人を特定する情報は一切含まれません。データにはRealmのバージョン、(iOS、OS Xなどの)プラットフォーム、プログラミング言語、などの情報が含まれ、それはRealmの製品の向上にのみ利用されます。統計データの送信は、リリースビルド、またはデバイス上で動作している時には行われません。あくまでも、開発中にデバッガに接続している時と、シミュレータ上で動作している時だけ送信されます。実際にどのようなデータを収集しているのかは、ソースコードから確認していただけます。

トラブルシューティング

クラッシュレポート

私たちは開発者の方にクラッシュレポーターを使用していただくことを推奨しています。Realmの操作のうちの多くは(ディスクI/Oを伴う操作などと同様に)実行時に失敗する可能性があります。そのため、クラッシュレポートを収集することは、何が原因で問題が発生したのかを特定し、エラー処理の改善や不具合の修正に有効です。

多くの商用のクラッシュレポーターはログを収集するオプションを提供しています。この機能を有効にすることを強く推奨します。Realm例外が発生した時や、致命的な状態に陥った際にはメタデータの情報をログに出力しており(そこにユーザーデータは一切含まれていません)、それらのログは我々が問題を調査する際に役に立ちます。

Realmの問題や不具合を報告するには

Realmについて問題を発見した際は、GitHubで問題を報告するかhelp@realm.ioにEメール、またはSlackで報告してください。その際には、こちらで問題を再現できるように、できるだけ多くの情報をあわせて教えてください。

下記に示す情報は問題を解決するために非常に役に立ちます。

  1. あなたが実際にやりたいこと・目的。
  2. 期待している結果。
  3. 実際に発生した結果。
  4. 問題の再現方法。
  5. 問題を再現、または理解できるサンプルコード (そのままビルドして実行できるXcodeプロジェクトだと理想的です)
  6. Realm、Xcode、OS Xのバージョン
  7. (利用している場合は)CarthageやCocoaPodsなどのパッケージマネージャのバージョン
  8. 問題の発生するプラットフォーム(iOSやtvOSなど)、OSのバージョン、アーキテクチャ(例: 64-bit iOS 8.1)。
  9. クラッシュログやスタックトレース。上述のクラッシュレポートの項目も参考にしてください。

Realmの再インストール(CarthageやCocoaPodsを使っている場合)

RealmをCocoaPodsまたはCarthageなどのパッケージマネージャを使ってインストールしていて、ビルドエラーが起こる場合、次の2つの原因が考えられます。 Realmが対応していないバージョンのパッケージマネージャを使用していてセットアップに失敗している、あるいはビルドツールやXcodeの古いキャッシュが残ってることが原因です。

その場合は、パッケージマネージャによって作成されたフォルダを削除してから、再インストールしてください。

また、Xcodeの Derived Data ディレクトリの削除と、 ビルドディレクトリの削除 も実行してみてください。そうすることで、ツールチェーンをアップデートしたり、ターゲットを新しく追加したことによる不整合や、複数のターゲットでライブラリを共有している場合などに起こるキャッシュの問題を解決することができます。ビルドディレクトリを削除するには、’Option’キーを押したままXcodeの’Product’メニュー>’Clean Build Folder…‘と選択します。もしくは、’Help’メニューの検索エリアに’Clean’と入力して検索結果に表示された’Clean Build Folder…‘を選択する方法もあります。

CocoaPodsを使っている場合

RealmをインストールするにはCocoaPods 0.39.0以降が必要です。

CocoaPodsを利用していて問題が起きた場合は、CocoaPodsによるライブラリとプロジェクトとの統合を解除してもう一度やり直してください。統合を解除して再インストールするには、ターミナルを起動し、下記のコマンドをプロジェクトのルートディレクトリで実行します。

pod cache clean Realm
pod cache clean RealmSwift
pod deintegrate || rm -rf Pods
pod install --verbose
rm -rf ~/Library/Developer/Xcode/DerivedData

Podsディレクトリを削除する代わりに、cocoapods-deintegrateプラグインを利用することもできます。CocoaPods 1.0ではこのプラグインは最初からCocoaPodsに組み込まれています。それ以前のバージョンのCocoaPodsを利用している場合は、gem install cocoapods-deintegrateというコマンドでインストールすることができます。そのあとはpod deintegrateとすると、CocoaPodsがプロジェクトに加えた変更が取り除かれます。

また、DerivedDataディレクトリの削除や、Buildディレクトリをクリーンすることも有効です。そうすることでCocoaPodsに起因する問題が解決したと非常に多く報告されています。Buildディレクトリをクリーンする方法は、’Optionキー’を押しながら、’Product’の’Clean Build Folder…‘を選択します。’Help’の検索メニューに”Clean”と入力することで表示される’Clean Build Folder…‘メニュー項目を選択するという方法もあります。

Carthageを使っている場合

RealmをインストールするにはCarthage 0.9.2以降が必要です。

Carthageによってインストールされたすべてのライブラリを削除して再インストールするには、ターミナルを起動し、下記のコマンドをプロジェクトのルートディレクトリで実行します。

rm -rf Carthage
rm -rf ~/Library/Developer/Xcode/DerivedData
carthage update

Realm Coreのダウンロードに失敗する場合

(特にCocoaPodsを使っていて)Realmがビルドされるときには、自動的にRealm Coreライブラリをスタティックライブラリとしてダウンロードし、Realm-Cocoaプロジェクトに組み込みます。 下記のようなメッセージが表示された場合は、ビルド時のCoreライブラリのダウンロードに失敗しています。

Downloading core failed. Please try again once you have an Internet connection.

このエラーが起こるのは、下記のいずれかの理由によります。

  1. 米国輸出規制法のリストに含まれる国のIPアドレスが割り当てられている。 米国の法律に従うために、Realmは上記のリストに含まれる国で使用することはできません。 さらに詳しいことはライセンス条項をお読みください。
  2. 中国国内、あるいは国レベルのファイアウォールによってCloudFlareまたはAmazon AWS S3へのアクセスを制限している国からアクセスしている。 さらに詳しいことはこちらのIssueをご覧ください。
  3. Amazon AWS S3が障害により一時的に利用できなくなっている。AWS Service Health Dashboardを見てサービスの稼働状況を確認し、 障害が解決された後で再度やり直してください。

少ないメモリの制限でRealmを利用するには

watchOSやApp Extensionといった環境では、使用可能なメモリが非常に少なく制限されています。そのような環境でRealmを使用するには、下記のようにConfigurationオブジェクトに使用するモデルクラスのリストを明示的に渡すことを推奨します。そうすることで、モデルクラスを調べるためにメモリの使用量が大きいobjc_copyClassList()が使用されなくなるからです。

RLMRealmConfiguration *config = [RLMRealmConfiguration defaultConfiguration];
config.objectClasses = @[Dog.class, Person.class];
RLMRealm *realm = [RLMRealm realmWithConfiguration:config error:nil];