This is not the current version. View the latest documentation

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

// 通常のC#のクラスと同じように定義します
public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person Owner { get; set; }
}

public class Person : RealmObject
{
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

var realm = Realm.GetInstance();

// 検索にはLINQクエリが使用できます
var puppies = realm.All<Dog>().Where(d => d.Age < 2);

puppies.Count(); // => 0 because no dogs have been added yet

// トランザクションを用いてオブジェクトを保存・更新します
realm.Write(() =>
{
    realm.Add(new Dog { Name = "Rex", Age = 1 });
});

// 検索結果は自動的にリアルタイムに更新されます
puppies.Count(); // => 1

// LINQクエリの構文はほとんど同じように使えます
var oldDogs = from d in realm.All<Dog>() where d.Age > 8 select d;

// 別のスレッドで検索や更新をすることもできます
new Thread(() =>
{
    var realm2 = Realm.GetInstance();

    var theDog = realm2.All<Dog>().Where(d => d.Age == 1).First();
    realm2.Write(() => theDog.Age = 3);
}).Start();

はじめに

Realmを使用するにはNuGetを利用してインストールします。ソースコードはGitHubにあります。

必要条件

下記の開発環境をサポートしています:

  • Visual Studio 2015以上、またはXamarin Studio 6.1.1以上
  • Xamarin.iOS 10.0.1.10以上、iOS 7以上、native UIとXamarin Formsの両方をサポート
  • Xamarin.Android 7.0.1.3以上、API level 10以上、native UIとXamarin Formsの両方をサポート
  • Windows Desktop(win32-x86とwin64-x64).
  • Xamarin.Macは未サポートですが、まもなく対応します。

Xamarinの開発は常に進んでいるため、サポートするバージョンはRealmのリリース時のStableアップデートチャネルに基づきます。Xamarinのマイナーバージョンが異なるくらいなら、RealmはXamarinの機能には密接に依存していないので問題になることはまずありません。ただし、XamarinのBetaチャネルまたはAlphaチャネルを使用すると問題が発生する恐れがあります。

PCLを利用している方へ重要なお知らせ - RealmとPCLは「NuGet Bait and Switch Trick」という記事で紹介されているテクニックによって動作します。そのためには、RealmをNuGetを用いて、すべてのRealmを使用しているPCLにインストールする必要があります。

共有プロジェクトを使用している場合は、そのプラットフォームに対応したRealmをNuGetを用いてインストールしてください。

Android ABIサポート

いくつかの命令セットに制限があるので、armeabiABI設定を サポートしていません

Xamarinプロジェクトを新規作成する際のデフォルトテンプレートではデバッグビルドではすべてのABI設定が有効になっていますが、リリースビルドではarmeabiだけが有効になっています。 リリースビルドではこの設定を変更する必要があります

ABI設定を正しく行っていない場合、特定のアーキテクチャのデバイスでSystem.TypeInitializationExceptionが発生することがあります。たとえば、Galaxy Tab S2などの64 bitデバイスにデプロイすると、armeabiarmeabi-v7a有効で、arm64-v8a無効の場合 はそのエラーが発生します。

他のABIをリンクする特別なな理由がなければ、armeabi以外の すべて のABIを有効にすることが最善の方法です。

  • armeabi-v7a
  • arm64-v8a
  • x86
  • x86_64

Xamarin Studioでは、この設定はプロジェクトオプション>ビルド>Androidビルド>詳細タブを右クリックすることで変更できます。

Visual Studioでは、この設定はプロジェクトプロパティ>Androidオプション>詳細タブを右クリックすることで変更できます。

インストール

ナイトリービルドを利用するにはNuGetの取得先にhttps://www.myget.org/F/realm-nightly/を指定します。VS2015またはXamarin Studio 6.1でNuGet バージョン3を使用しているならhttps://www.myget.org/F/realm-nightly/api/v3/index.jsonを指定します。

  1. ソリューションペインのプロジェクトの下にある”Packages”の歯車の形のボタンをクリックし、”Add Packages…“を選択します。
  2. 検索フィールドに”Realm”と入力します。
  3. Realmを選択し、追加します。
  4. Fodyが依存関係として追加されたことを確認します。

Realmパッケージには必要なものがすべて含まれています。RealmはFodyというWeavingのためのライブラリを依存関係として追加します。FodyはRealmObjectを永続化可能にするために必要です。

ここまででパッケージがインストールされます。もし、すでにプロジェクトでFodyを利用している場合は、もともとのFodyWeavers.xmlを更新する必要があります。これはRealmWeaverを含め、すべてのWeavingの設定を使えるようにするために必要な手続きです。

下記はすでにFodyをプロジェクトで使用したいた場合に、Realmの設定を有効にするためのFodyWeavers.xmlの例です。

<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
    <RealmWeaver />
</Weavers>
  1. プロジェクトにて、ソリューションからTools - NuGet Package Manager - Manage Packagesと選択します。
  2. インストール可能なパッケージの一覧から、Realmパッケージを選択します。
  3. 右側にプロジェクトがチェックされていることと、インストールボタンが押せることを確認します。
  4. インストールボタンを押します。
  5. ダイアログが表示されたらOKボタンを押します。このダイアログはRealmとFodyをインストールするかどうかの確認が表示されています。

Realmパッケージには必要なものがすべて含まれています。RealmはFodyというWeavingのためのライブラリを依存関係として追加します。FodyはRealmObjectを永続化可能にするために必要です。

ここまででパッケージがインストールされます。もし、すでにプロジェクトでFodyを利用している場合は、もともとのFodyWeavers.xmlを更新する必要があります。これはRealmWeaverを含め、すべてのWeavingの設定を使えるようにするために必要な手続きです。

下記はすでにFodyをプロジェクトで使用したいた場合に、Realmの設定を有効にするためのFodyWeavers.xmlの例です。

<?xml version="1.0" encoding="utf-8" ?>
<Weavers>
    <RealmWeaver />
</Weavers>

Realm Browser

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

現在のRealm Xamarinがサポートしているファイルフォーマットは、少し古いフォーマットです。最新のRealm Browserでファイルを開いてしまうと、そのファイルは現在のRealm Xamarinでは開くことができなくなります。上記のリンクは現在のRealm Xamarinと互換性のあるRealm Browserのバージョンです。

Realm Browser

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

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

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

API Reference

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

サンプルコード

GitHubリポジトリのexamplesフォルダにたくさんのサンプルコードがあります。ぜひ、ご参考にしてください。

ヘルプ

  • 使い方に困ったときは、StackOverflowで#realmタグを付けて質問してください。私たちは毎日StackOverflowをチェックしています。
  • さらに複雑な問題に対する質問は、こちらの Slackチャットにて聞いてください。(質問は日本語で構いません)
  • バグ報告や機能リクエストについては GitHubのIssuesからご報告ください。

モデル

Realmのデータモデルは、普通のC#のクラスやプロパティとして定義できます。モデルオブジェクトを定義するには、単にRealmObjectのサブクラスを作成するだけです。

Realmのモデルオブジェクトは、他のC#のオブジェクトとほとんど同様に機能します。一般的なクラスと同様にメソッドやイベントを追加し、同じように使用することができます。主な制限はオブジェクトは作成したスレッド以外では使用できないことと、永続化するプロパティはgetterとsetterを生成する必要があることです。

さらに、モデルクラスはPublicかつ引数なしのコンストラクタを持っていなければなりません。カスタムのコンストラクタを1つも定義しなければ、コンパイラが自動的に引数なしのコンストラクタを追加します。 しかし、もし1つでも自分でコンストラクタを定義した場合は、合わせて引数なしのコンストラクタも定義しなければなりません。

関連とネストしたデータ構造は、単純に関連の対象となるモデルクラスのプロパティを持たせる、対象のモデルクラスを保持するIListをプロパティとして持たせます。

// Dog model
public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
    public Person Owner { get; set; }
}

public class Person : RealmObject
{
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

対応しているデータ型

Realmは符号なし整数型以外のプリミティブ型(boolcharbyteshortintlongfloatdouble)とstring、そしてDateTimeOffset型をサポートしています。Null許容型も同様にサポートしています。詳しくはNull許容型のプロパティセクションで説明しています。

Date型

日付を表す型としてはDateTime型ではなく標準のDateTimeOffset型を採用しています。

このことはMicrosoftによって曖昧さがあるDateTimeよりも推奨されていることに基づいています。

実際の値は100ナノ秒の精度で保存されます。

タイムゾーンを指定してDateTimeOffsetを保存できますが、RealmにはUTCの値が保存されます。他のプラットフォームとの互換性のために曖昧さのない表現を採用しています。タイムゾーン指定子がなくなるので、UtcTicksの代わりに Ticksを使って値を比較すると正しくないことがあります。

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

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

IList型のプロパティについては、 IListプロパティを定義すると最初に使用されたときに実際のインスタンスが作成されます。自分でインスタンスを作成することはできません。そのためget;だけを定義しなければなりません。

1対1のリレーションシップ

モデル間で1対1や多対1の関連を持たせるには、別のRealmObjectのサブクラスのプロパティを定義します。

public class Dog : RealmObject
{
    // ... other property declarations
    public Person Owner { get; set; };
}

public class Person : RealmObject
{
    public string Name { get; set; }
}

関連として定義したプロパティの使い方は、他のオブジェクトと同様に代入するだけです。

realm.Write(() =>
{
    var jim = realm.Add(new Person { Name = "Jim" });

    var rex = realm.Add(new Dog { Owner = jim });
});

関連を解除するには単にnullを代入します。

rex.Owner = null;

RealmObjectをプロパティとして持つとき、ネストされたプロパティには通常のオブジェクトのプロパティと同じ文法でアクセスできます。上記の例では、rex.Owner.Address.Countryとすると、Realmはネストしたオブジェクトを必要に応じて自動的にフェッチします。

1対多のリレーションシップ

1対多の関連を定義するにはIListをプロパティとして定義します。このプロパティにアクセスすると、空のRealmListか、単一のRealmObject型を要素に持つRealmListが返ります。

例えば、PersonクラスがDogオブジェクトを複数持てるように、1対多の関連として“Dogs”を追加するとすると、IList<Dog>をプロパティとして定義するだけです。IListオブジェクトを自分で生成することはできません。 – Realmが自動的にオブジェクトを生成します。関連のオブジェクトについては要素のオブジェクトを追加または削除する操作のみ行うことができます。

public class Dog : RealmObject
{
    public string Name { get; set; }
}

public class Person : RealmObject
{
    // ... other property declarations
    public IList<Dog> Dogs { get; }
}
jim.Dogs.Add(rex);
jim.Dogs.Count();  // => 1 -- nobody but rex

逆方向の関連

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

Backlink(逆方向の関連)プロパティを使用することで、関連元のオブジェクトを特定のプロパティを使って取得することができます。例えば、DogクラスにOwnersという自分自身を関連として保持しているPersonオブジェクトをすべて取得するというプロパティを持たせることができます。その方法はOwnersプロパティをIQueryable<Person>型として定義し、[Backlink]属性を用いてOwnersPersonクラスの関連であると指定するだけです。

public class Dog : RealmObject
{
    [Backlink(nameof(Person.Dogs))]
    public IQueryable<Person> Owners { get; }
}

Backlink(逆方向の関連)プロパティはIList<RealmObject>型(1対多の関連)とRealmObject型(1対1の関連)のプロパティのどちらにも使えます。

public class Ship : RealmObject
{
    public Captain Captain { get; set; }
}

public class Captain : RealmObject
{
    [Backlink(nameof(Ship.Captain))]
    public IQueryable<Ship> Ships { get; }
}

Null許容型のプロパティ

stringbyte[]や関連に用いるRealmObjectのような参照型はnull値をとることができます。

int?のようなNull許容型やNull許容型のDateTimeOffset?も完全にサポートしています。

プロパティの永続性をコントロールする

RealmObjectを継承したクラスはFody weaverによってコンパイル時に処理されます。そのとき、すべての自動実装プロパティは永続化の対象であると推定され、Realmの内部ストレージとマッピングするためのsetterとgetterが自動的に生成されます。

プロパティの永続化をコントロールするためのメタデータを付加するC#属性をいくつか提供しています。

プロパティが永続化されないようにするには、[\[Ignored\]](/docs/dotnet/1.0.3/api/reference/Realms.IgnoredAttribute.html)属性を付加します。よくある例としては、画像を保存するときに、実際のデータではなくパスだけを保存するような場合です。

public string HeadshotPath { get; set; }

// 画像データは永続化されず、メモリ上にのみ保持されます
[Ignored]
public Image Headshot { get; set; }

カスタムSetter

カスタムのsetterやgetter実装を持つプロパティは自動的に保存されないプロパティとして扱われます。例えば次のようなバリデーションをsetterに追加することができます。

private string Email_ { get; set; }

// Validating version of persistent Email_ property
public string Email
{
    get { return Email_; }
    set
    {
        if (!value.Contains("@")) throw new Exception("Invalid email address");
        Email_ = value;
    }
}

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

現在のバージョンでは、文字列と整数型、bool型およびDateTimeOffset型のプロパティはインデックスに対応しています。

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

プロパティをインデックスに登録するには、[Indexed]属性をプロパティの定義に追加します。 (例) csharp public class Person : RealmObject { [Indexed] public string Name { get; set; } public IList<Dog> Dogs { get; } }

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

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

var myDog = new Dog { Name = "Fido", Age = 1 };
realm.Write(() =>
{
    realm.Add(myDog);
});

var myPuppy = realm.All<Dog>().First(d => d.Age == 1);
realm.Write(() =>
{
    myPuppy.Age = 2;
}

myDog.Age; // => 2

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

Realmの通知に登録することで、いつRealmのデータが更新されたのかが分かります。アプリケーションのUIを更新すべきタイミングを知ることができます。

プライマリキー・プロパティ

[PrimaryKey]属性はそのRealmObjectモデルクラスのプロパティに 1つにだけ 設定することができます。プライマリキーを定義すると、オブジェクトの検索と更新を効率よく行なうことができ、それぞれの値がユニークであるということも保証できます。

charと整数型、および、文字列型のプロパティのみ、プライマリキーとして指定できます。小さな整数値を指定したり、どの型を使っても、パフォーマンスや保存されるサイズには関係ありません。

複数のプロパティに[PrimaryKey]属性を付加すると、コンパイルされますが実行時に最初のRealmを開く際に Schema validation failed というエラーが発生します。

PrimaryKeyを指定したオブジェクトをRealmに保存した後は、そのオブジェクトIDを変更することはできなくなります。

同じプライマリキーの値を持つ別のオブジェクトを保存しようとすると、RealmDuplicatePrimaryKeyValueException例外が発生します。

プライマリキーが定義されていれば、Realm.Findを使ってオブジェクトをすばやく取得することができます。これは、LINQを使用するよりもより効率的なインデックスを使用したクエリが使われます。文字列、文字または整数キーに対応するためにそれぞれのオーバーロードが定義されています。

public class Person : RealmObject
{
    [PrimaryKey]
    public string SSN { get; set; }
    public string Name { get; set; }
    public IList<Dog> Dogs { get; }
}

var objByPK = realm.Find<Person>("457-55-5462");

保存しないプロパティ

[Ignored]属性を用いて、特定のプロパティをRealmに保存せずに、単なる普通のC#のオブジェクトのプロパティとして扱うことができます。 または、プロパティにSetterもしくはGetterメソッドを定義した場合も、自動的にそのプロパティは保存しないプロパティとして扱われます。

モデルクラスの継承

Realm Xamarinではモデルクラスをさらに継承することはできませんRealmObjectの直接のサブクラス以外に継承されたクラスがあるとWeavingに失敗します。

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

Realmオブジェクトの集合を表すには標準の型を使用します。

  1. IQueryable <T>は、クエリを用いて取得されたオブジェクトを表します。
  2. IList <T>は、モデル間の1対多の関係を表すクラスです。

実行時にはどちらもIRealmCollection\<T\>インターフェースIReadOnlyList<T>INotifyCollectionChangedに準拠し、遅延ロードに対応します。つまり、コレクションのサイズを取得したとしても、実際に要素のオブジェクトがメモリにロードされるのはその要素にアクセスしるときまで遅延されます。

書き込み

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

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

トランザクションには、無視できないオーバーヘッドが発生しますので、トランザクションの数はできるだけ最小限に抑えることが望ましいです。例えば、ループの中で複数のオブジェクトを追加する場合などは、ループの中で要素ごとにトランザクションを作るのではなく、ループの外に1つだけトランザクションを作る方がパフォーマンスが良くなります。

realm.Write(() =>
{
    var people = Realm.All<Person>();
    foreach (var person in people)
    {
        person.Age += 1;
    }
});

トランザクションはディスクI/Oを伴う操作などと同様に失敗する可能性があります。
ディスクの容量不足などで失敗した際のエラーから復帰するためには例外処理に備える必要があります。簡単にするためにこのドキュメントやサンプルコードではエラー処理をしていませんが、実際のアプリケーションでは、正しくエラーを処理するべきです。

トランザクションを開始するには2つの簡単な方法があります。[Realm.BeginWrite()](/docs/dotnet/1.0.3/api/reference/Realms.Realm.html#Realms_Realm_BeginWrite)[Realm.Write()](/docs/dotnet/1.0.3/api/reference/Realms.Realm.html#Realms_Realm_Write_System_Action_)です。
最初の方法、[Realm.BeginWrite()](/docs/dotnet/1.0.3/api/reference/Realms.Realm.html#Realms_Realm_BeginWrite)[Transaction](/docs/dotnet/1.0.3/api/reference/Realms.Transaction.html)オブジェクトを返します。`Transaction`オブジェクトは`Dispose`パターンを実装しているので、`using`とともに使うことができます。

```csharp
using(var transaction = realm.BeginWrite())
{
    person.FirstName = "John";
    person.Age = 56;
    transaction.Commit();
}

明示的にCommitメソッドをトランザクションオブジェクトに対して呼び出す必要があります。そうしなければ、トランザクションは自動的にロールバックされます。もう一つの方法は、Realm.Write()を用いて変更を加えるコードを囲む方法です。

realm.Write(() =>
{
    person.FirstName = "John";
    person.Age = 56;
});

この方法では、例外が起こらない限りはトランザクションは暗黙的にコミットされます。

オブジェクトの生成

RealmObjectのサブクラスとして定義したモデルクラスは、インスタンス化してRealmに保存することができます。例として下記のようなシンプルなモデルを考えます。

public class Dog : RealmObject
{
    public string Name { get; set; }
    public int Age { get; set; }
}

インスタンスをRealmの保存するにはRealm.Add()メソッドを使用します。保存されたインスタンスは自動更新されるようになります。

realm.Write(() =>
{
    var myDog = new Dog();
    myDog.Name = "Rex";
    myDog.Age = 10;
    realm.Add(myDog);
});

オブジェクトをRealmに追加した後は、オブジェクトに対するすべての変更は保存されます。(オブジェクトの変更は、トランザクションの中で行わなければなりません)。別のスレッドで、同じRealmに保存されているオブジェクトに変更があった場合は、トランザクションが完了した時点ですべての変更が適用されます。

同時に発生した書き込み処理は、互いの書き込み処理をブロックします。 これは類似の他のデータベースでも同様で、よく使われるベストプラクティスとして、書き込み処理を別のスレッドに分けることを推奨します。

RealmはMVCCアーキテクチャーを採用しているので、書き込み処理の最中でも読み込み処理をブロックすることはありません。同時に複数のスレッドから書き込みをするのでなければ、大きな単位でトランザクションを使いましょう。細かいトランザクションを使うよりこの特性を活かすことができます。

オブジェクトの更新

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

// トランザクションを開始して、オブジェクトを更新します
using (var trans = realm.BeginWrite())
{
    author.Name = "Thomas Pynchon";
    trans.Commit();
}

[PrimaryKey]を定義したオブジェクトについては、realm.Addメソッドの追加の引数としてupdate: trueを渡すことで、既存のオブジェクトを上書きできます。

public class Person : RealmObject
{
    [PrimaryKey]
    public int Id { get; set; }

    // ... other property declarations
}

realm.Write(() =>
{
    realm.Add(new Person
    {
        Id = 1,
        Name = "Kristian"
    });
});

var kristianWithC = new Person
{
    Id = 1,
    Name = "Christian"
};

realm.Write(() => realm.Add(kristianWithC, update: true));

update: trueを指定したにもかかわらず、PrimaryKeyを持たないオブジェクトを渡した場合は、上書きするオブジェクトを見つけられないので、update: falseを指定した場合と同じ挙動になります。 更新対象のオブジェクトが別のRealmObjectを関連として持っている場合、PrimaryKeyがある場合は追加または更新されます。PrimaryKeyが無い場合は単に追加されるだけになります。

var kristian = new Person { Id = 1, Name = "Kristian" };
var rex = new Dog { Id = 1, Name = "Rex" };
kristian.Dogs.Add(rex);

realm.Write(() => realm.Add(kristian));

var christian = new Person { Id = 1, Name = "Christian" };
christian.Dogs.Add(new Dog { Id = 1, Name = "Bethoven" });

realm.Write(() => realm.Add(christian, update: true));

var newName = kristian.Name; // Christian
var newDogName = kristian.Dogs.First().Name; // Bethoven

オブジェクトの削除

オブジェクトを削除するには、トランザクションの中で削除したいオブジェクトをRealm.Removeに渡します。

var cheeseBook = realm.All<Book>().First(b => b.Name == "Cheese");

// トランザクションを開始してオブジェクトを削除します
using (var trans = realm.BeginWrite())
{
    realm.Remove(cheeseBook);
    trans.Commit();
}

Realmに保存されてるオブジェクトをすべて削除することもできます。ディスクスペースを効率的に再利用するために、Realmファイルのサイズはそのまま維持されることに注意してください。

クエリ

クエリは標準のLINQ構文を実装しています。

まずrealm.All<T>メソッドに型を指定して、その型の全てのオブジェクトを取得します。これがもっとも基本的なクエリになります。それに対して、Whereやその他のLINQ演算子を繋げて、結果をフィルタすることができます。

Realmで使えるLINQ構文についてはLINQサポートページをご覧ください。

拡張構文とメソッドチェーン

var oldDogs = realm.All<Dog>().Where(d => d.Age > 8);

クエリ式

var oldDogs = from d in realm.All<Dog>() where  d.Age > 8 select d;

どちらの構文を利用した場合でも返されるオブジェクトはIQueryableインターフェースに準拠しています。そのため、下記のようにオブジェクトをイテレーションすることができます。

foreach (var d in oldDogs)
{
    Debug.WriteLine(d.Name);
}

より複雑なクエリのサンプル

もしLINQクエリの使い方にあまり慣れていない場合のために、下記にいくつかの基本的なクエリ構文を示します。 拡張構文 を用いて基本的なクエリを書く方法がわかります。

下記はPersonオブジェクトからJohnまたはPeterというファーストネームを持つオブジェクトをすべて検索する例です。

var johnsAndPeters = realm.All<Person>().Where(p =>
    p.FirstName == "John" ||
    p.FirstName == "Peter");
var peopleList = johnsAndPeters.ToList();

最初の文に書かれているjohnsAndPetersという変数のオブジェクトはIQueryableを実装しています。”John”または”Peter”というファーストネームを持つオブジェクトが格納されています。

これが標準的なLINQクエリの実装です。クエリによって条件に合うオブジェクトを取得することができます。クエリは遅延実行されるので、結果のオブジェクトをループしたり、件数を取得しようとしなければ実際の処理は何も実行されることはありません。

最後に呼び出しているToListメソッドで、Realmコアエンジンからのマッピングを切り離しています。

オブジェクトはコピーされません。条件に合うオブジェクトの参照を取得します。

すべてのオブジェクトをリストとして取得する代わりに、クエリの結果オブジェクトをC#標準のforeach分を用いてループすることもできます。

foreach (var person in johnsAndPeters) // iterate query
{
    Debug.WriteLine(person.Name);
}

論理演算子

C#標準の論理演算子をLINQクエリで利用できます。

カッコを使用して、ネストした条件や優先順位を示すことができます。

var complexPeople = realm.All<Person>().Where(p =>
    p.LastName == "Doe" &&
    (p.FirstName == "Fred" || p.Score > 35));

型を指定してオブジェクトを取得する

指定した型のオブジェクトをすべて取得するには、realm.All<T>()メソッドを使用します。指定したすべてのモデルクラスを保持したIQueryableコレクションオブジェクトが返ります。上記の例ではPersonクラスのオブジェクトすべてが返ります。

さらに取得したオブジェクトを絞り込むために重ねてLINQクエリを適用することができます。遅延実行されるので、コレクションをループしたりしない限りは何のオーバーヘッドもありません。

var ap = realm.All<Person>();  // this is all items of a type
var scorers = ap.Where(p => p.Score > 35);  // restrict with first search clause
var scorerDoe = scorers.Where(p => p.LastName == "Doe");  // effectively an AND clause

並べ替え

標準のLINQクエリのようにOrderByOrderByDescendingThenByThenByDescendingを用いて、複数レベルの並び順をQueryableに対して指定することができます。

並べ替えは内部クエリエンジンにて非常に効率良く実行されます。並べ替えを実行するためにすべてのオブジェクトをロードすることはありません。また、単にRealm.Allで取得したすべてのオブジェクトを並べ替える場合にはWhereを使う必要はありません。

注意: 取得したオブジェクトをリストに変換するためにToList呼び出した後 に、OrderByなどを用いて並べ替えを行うと 、 _標準のLINQ によりメモリ上で並べ替えが実行されます。ToListを使う場合は、構文の最後で実行するように気をつけてください。

var highestScore = realm.All<Person>().OrderByDescending(p => p.Score).First();

var sortedPeople = realm.All<Person>()
    .OrderBy(p => p.LastName)
    .ThenBy(p => p.FirstName);

クエリの連鎖

取得したオブジェクトはけっしてコピーされず、必要に応じで計算は遅延されるので、複雑なクエリも、非常に効率良くチェーンして分かりやすく書くことができます。

var teenagers = realm.All<Person>().Where(p => p.Age >= 13 && p.Age <= 20);
var firstJohn = teenagers.Where(p => p.FirstName == "John").First();

取得データの数を制限

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

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

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

LINQが備えているTakeを使いたいと思うかもしれませんが、その機能はまだサポートされていません。使えるようになった際には、一回の操作で要求したすべてのオブジェクトをインスタンス化するものになるでしょう。

Realmについて

Realmとはデータベースと同等の機能を持ち、複数の種類のオブジェクトをファイルとしてディスクに保存することができます。

デフォルトRealm

すでにお気づきかもしれませんが、これまでに示したコード例ではRealm.GetInstance(string optionalPath)optionalPath引数を指定することなく使っています。このStaticメソッドは、Environment.SpecialFolder.Personalが指すパスに作られたdefault.realmという名前のファイルを指すRealmインスタンスをスレッドごとに返します。

Realm Configuration

Realm.GetInstance()を利用することで簡単にRealmを使うことができます。さらにきめ細やかなカスタマイズをしたい場合は、RealmConfigurationオブジェクトを利用します。作成されるRealmの設定をカスタマイズすることができます。

RealmConfigurationを使ってファイルのパスを指定する方法はいくつかあります。(Configurationオブジェクトを一度作成したら後からパスの値を変えることはできません。)

  1. 絶対パスを指定して、ファイルパスを完全に変更する。
  2. 相対パスでディレクトリを指定して、Realmファイルを標準のパスの下層に作成する。
  3. ファイル名だけを指定して、保存場所は変えずにファイル名だけを変更する。

RealmConfigurationオブジェクトではでは、スキーマバージョンを指定できます。 詳細については、マイグレーションのセクションをご覧ください。 開発中の利便性のために、既存のファイルと現在のスキーマが一致しないとき(マイグレーションが必要になるとき)に自動的に既存のファイルを削除し、新しいスキーマで作り直す機能があります。その機能を有効にするにはShouldDeleteIfMigrationNeededプロパティをtrueに設定します。すると、Realm.GetInstance()はスキーマが既存のファイルと一致しない場合は既存のファイルを自動的に削除します。リリースする前にモデルの構造を簡単に試行錯誤できます。リリースするアプリではこのプロパティを 有効にしないことを推奨します 。 意図せずデータが消えてしまうという事態を避けるために#if DEBUGセクション内に設定することも良い方法です。

同じConfigurationオブジェクトをRealmをオープンする際に渡すことで、すべてのRealmを同じ設定で使うことができます。この方法は、複数のスレッドで同じRealmを扱うもっとも一般的な方法です。

Configurationオブジェクトを渡して回る代わりに、デフォルトConfigurationをオーバーライドすることで、すべて同じ設定を適用することもできます。

RealmインスタンスはConfiguration単位、スレッド単位のシングルトンであることに注意してください。つまり、Realm.GetInstance()は、同じConfiguration、かつ同じスレッド上で呼び出されるなら同じインスタンスを返します。

Realmインスタンスをクローズする

Realmはネイティブメモリとファイルディスクリプタの開放を行うためにIDisposableを実装しています。そのため、インスタンスがスコープの外に出たときに、自動的にクローズされます。

Realmファイルの探し方

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

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

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

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

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

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

モデル定義のサブセット

各Realmファイルで保存されるモデルの定義を使い分けたり、制限したいことがあります。

下記のように、RealmConfigurationオブジェクトのObjectClassesプロパティを用いて、それぞれのRealmに保存されるモデルクラスを制限することができます。

class LoneClass : RealmObject
{
    public string Name { get; set;}
}

var config = new RealmConfiguration("RealmWithOneClass.realm");
config.ObjectClasses = new Type[] {typeof(LoneClass)};

// Realmに保存されるクラスを指定した2つだけにする
config.ObjectClasses = new Type[] {typeof(Dog), typeof(Cat)};

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

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

普通のファイルと異なり、Realmのファイルはメモリにマッピングされており、RealmファイルはRealmインスタンスが生存している間、有効でなければなりません。

アプリケーションコードで、Realmの細かなファイルについて意識しなくて済むように、Realm.DeleteRealm(RealmConfiguration)という便利なメソッドを提供しています。

var config = new RealmConfiguration("FileWeThrowAway.realm");
Realm.DeleteRealm(config);
var freshRealm = Realm.GetInstance(config);

ファイルサイズを最適化(コンパクション)する

アプリケーションの使用中に、Realmによって使用されるメモリが断片化され、必要以上に多くの領域が占有される可能性があります。内部ストレージを再編成し、可能ならファイルサイズを減らすために、Realm.Compact(config)を利用します。

var config = new RealmConfiguration();
var initialSize = new FileInfo(config).Length;
if (initialSize > 100 * 1024 * 1024) // 100 MB
{
    Realm.Compact(config);
    var newSize = new FileInfo(config).Length; // Should be less than initialSize
}

コンパクションに関する注意事項

  • コンパクションの対象ファイルを開いているRealmインスタンスがある場合はコンパクションは実行できません。
  • ディスクには同じRealmファイルをコピーできるだけの空き容量が必要です。
  • コンパクションに失敗した場合、Realmファイルはそのままで変更されません。
  • このメソッドは成功するとtrueを返し、他に同じファイルを開いているRealmインスタンスがある場合はfalseを返します。
  • 現時点ではコンパクションを実行した結果のファイルサイズを見積もるためのAPIは提供していません。推奨する方法としては、起動時にファイルのサイズをチェックし、一定の値を超えた場合にコンパクションを実行します。

マルチスレッド

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

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

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

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

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

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

また、トランザクションがコミットされたときも最新のデータが反映されます(Transaction.Commit())。

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

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

永続化されたRealmRealmObjectRealm.Allメソッドから取得されたIQueryableオブジェクト、またはRealmObjectIListプロパティのオブジェクトは、生成されたスレッド内でなければ利用することができません。 別のスレッドで利用されると例外が発生します。これはRealmがトランザクションを分離するための必要な仕様です。

スレッド間をまたいでオブジェクトを受け渡すための方法を紹介します。例えば、PrimaryKeyを用いてRealmObjectを取得し直す、またはRealmConfigurationを用いて、各スレッドごとにRealmインスタンスを取得します。対象のスレッドで取得したインスタンスは、もともとのスレッドとは別の時点のトランザクションのデータである可能性があるので、注意してください。

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

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

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

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

Realmオブジェクトはスレッドセーフではないため、複数のスレッド間で共有することができません。それぞれのスレッド/ディスパッチキューでRealmオブジェクトを生成する必要があります。

通知

Android環境では、ChangeリスナーはLooper持つスレッドでのみ動作します。Looperスレッドを持たないスレッドではRealmを自分で呼び出す必要があります。

Realmに対する通知

リスナーに登録しておくと、Realmインスタンスは、他のスレッドやプロセスでトランザクションが完了するたびに、他のRealmインスタンスに対して通知を送ります。

realm.RealmChanged += (s, e) =>
{
    // Update UI
}

現在はRealmChangedによる通知は変更内容の情報は提供されないことに注意してください。変更内容を知る必要があるならオブジェクトに対する通知またはコレクションに対する通知を使用します。

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

RealmObjectクラスはINotifyPropertyChangedインターフェースに準拠しているので、プロパティの変更を監視できます。プロパティに変更があるたびにPropertyChangedEventメソッドが呼ばれます。

public class Account : RealmObject
{
    public long Balance { get; set; }
}

下記のように、PropertyChangedEventイベントをクラスに対して監視すると、Balanceプロパティに変更があるたびにイベントハンドラが呼び出されます。

var account = new Account();
realm.Write(() => realm.Add(account));
account.PropertyChanged += (sender, e) =>
{
    Debug.WriteLine($"New value set for {e.PropertyName}");
}

realm.Write(() => account.Balance = 10); // => "New value set for Balance"

監視を設定するタイミングは、Realmにオブジェクトを保存する前でも後でもどちらでも構いません。変更のイベントはどちらの場合でも同じように発生します。

これだけでも便利な機能ですが、さらにXamarin.Formsを使ったデータバインディングも可能です。詳細は、XamarinドキュメントのデータバインディングからMVVMへを参照してください。

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

Realm.All<T>()メソッド、またはIQueryableLINQクエリから取得したIQueryableオブジェクトはは自動更新されます。常に最新の状態に自動的にアップデートされるということです。アクセスしたときには常に最新の変更が反映された値が取得されます。

同様に、RealmObjectIListプロパティは、自動更新される1対多の関連です。つまり、アクセスするたびに、関連オブジェクトの最新のコレクションが取得されます。

これらのコレクションの実際のオブジェクトはIRealmCollection<T>に準拠しています。つまり変更を監視するための2つの仕組みが使えることを意味しています。Realmでは便利な拡張メソッドとしてAsRealmCollectionを提供しています。このメソッドを用いてIList<T>またはIQueryable<T>IRealmCollection<T>にキャストできます。

.NET標準のINotifyCollectionChangedを使用するとMVVMデータバインディングの仕組みとうまく連携できます。IRealmCollection<T>をビューに直接渡すことができます。

UITableView ListViewを直接(例えばXamarin Native UIプロジェクトで)操作するのに便利な、より詳細な変更情報が必要なら、SubscribeForNotifications拡張メソッドを使用します。このメソッドには、変更セットを使用して呼び出されるデリゲートから追加、削除、または変更されたことを取得できます。

realm.All<Person>().SubscribeForNotifications ((sender, changes, error) =>
{
    // Access changes.InsertedIndices, changes.DeletedIndices, and changes.ModifiedIndices
});

マイグレーション

データベースを使ってる場合、時間が経つにつれ、データモデルは変更されていくものです。 Realmでのデータモデルは、標準的なC#インターフェースで定義されていますので、インターフェースに変更を加えるだけで、簡単にデータモデルを変えられます。 例えば次のようなPersonモデルがあるとします。

public class Person : RealmObject
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int Age { get; set; }
}

ここでFirstNameプロパティとLastNameプロパティを統合して1つのFullNameに変更したくなったとします。その場合モデルのインターフェースを次のように変更します。

public class Person : RealmObject
{
    public string FullName { get; set; }
    public int Age { get; set; }
}

このとき、元のデータモデルを使ってすでにデータを保存していた場合は、既存のRealmファイルとコード上のクラス定義に不一致が発生します。ファイルを開こうとした際に不一致が検出されると例外が発生します。その場合はマイグレーションを実行しない限りは続行できません。

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

スキーマの不一致を解決するもっとも簡単な方法はスキーマバージョンを上げることです。スキーマバージョンを指定しなかった場合は自動的にゼロがセットされています。モデル定義を変更した場合は、スキーマバージョンを前よりも大きな値にセットします。スキーマバージョンの指定はConfigurationオブジェクトのプロパティに設定します。これはモデルの変更が意図的かどうかを判断するために必要な作業になります。

var config = new RealmConfiguration() { SchemaVersion = 1 };

var realm = Realm.GetInstance(config);

この状態で既存のファイルを開くと、PersonモデルのFirstNameプロパティとLastNameプロパティに記録されたデータはすべて削除され、新しくFullNameプロパティが追加されます。FullNameプロパティには空の文字列が設定されます。Ageプロパティは以前のデータがそのまま残っています。おそらく本当に実現したいことは古いデータを移行することでしょう。そのためにはRealmのマイグレーションハンドラにデータを移行する処理を書かなければなりません。マイグレーションハンドラはMigrationCallback関数を用います。この関数はMigrationオブジェクトを引数として受け取ります。Migrationオブジェクトは2つのRealm型のプロパティ、OldRealmNewRealmを持ちます。これらを使って古いデータモデルから新しいデータモデルにデータをコピーする処理を記述します。

クラスやプロパティの名前を変更した場合も、Realmは自動的に何が変更されたかは検知できないので、通常はマイグレーション処理を記述する必要があります。

このコールバック関数は古いスキーマバージョンのファイルを開く際に1回だけ呼び出されます。複数のバージョンに渡るマイグレーションがある場合は、すべてのバージョンに対してマイグレーションを行う必要があります。

この例では新しいPersonクラスはFirstNameプロパティとLastNameプロパティが無くなっているので、そのデータに通常の方法ではアクセスできません。そこで動的APIを利用します。

var config = new RealmConfiguration
{
    SchemaVersion = 1,
    MigrationCallback = (migration, oldSchemaVersion) =>
    {
        var newPeople = migration.NewRealm.All<Person>();

        // Use the dynamic api for oldPeople so we can access
        // .FirstName and .LastName even though they no longer
        // exist in the class definition.
        var oldPeople = migration.OldRealm.All("Person");

        for (var i = 0; i < newPeople.Count(); i++)
        {
            var oldPerson = oldPeople.ElementAt(i);
            var newPerson = newPeople.ElementAt(i);

            newPerson.FullName = oldPerson.FirstName + " " + oldPerson.LastName;
        }
    }
};

var realm = Realm.GetInstance(config);

さらにアプリが更新されて、モデルに対して別の変更が行われスキーマバージョンを増加させたとします。アプリがすべて最新の状態に更新されているとは限らないので、oldSchemaVersionパラメータをチェックして、必要なマイグレーションを順番に実行しなければなりません。

ここではPersonクラスにさらに変更を加え、Birthdayプロパティを追加したとします。

public class Person : RealmObject
{
    public string FullName { get; set; }
    public int Age { get; set; }
    public DateTimeOFfset Birthday { get; set; }
}

上記は変更したモデルです。マイグレーションの処理は下記のようになります。

var config = new RealmConfiguration
{
    SchemaVersion = 2,
    MigrationCallback = (migration, oldSchemaVersion) =>
    {
        var newPeople = migration.NewRealm.All<Person>();
        var oldPeople = migration.OldRealm.All("Person");

        for (var i = 0; i < newPeople.Count(); i++)
        {
            var oldPerson = oldPeople.ElementAt(i);
            var newPerson = newPeople.ElementAt(i);

            // Migrate Person from version 0 to 1: replace FirstName and LastName with FullName
            if (oldSchemaVersion < 1)
            {
                newPerson.FullName = oldPerson.FirstName + " " + oldPerson.LastName;
            }

            // Migrate Person from version 1 to 2: replace Age with Birthday
            if (oldSchemaVersion < 2)
            {
                newPerson.Birthday = DateTimeOffset.Now.AddYears(-(int)oldPerson.Age);
            }
        }
    }
};

var realm = Realm.GetInstance(config);

暗号化

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暗号化方式でデータベースファイルを暗号化する機能を提供しています。

var config = new RealmConfiguration("Mine.realm");
config.EncryptionKey = new byte[64] // キーは必ずこの大きさでなければなりません
{
  0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
  0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
  0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
  0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38,
  0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
  0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
  0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
  0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78
};

var realm = Realm.GetInstance(config);  // "Mine.realm"というファイル名で暗号化されたRealmファイルを作成します

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

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

アプリケーションごとに異なるキーを使ってください。 けっして 上記のサンプルコードをコピー&ペースト しないでください 。ユーザーごとに異なるキーを使用したい場合は、Xamarin.Auth APIを参考にしてください。

暗号化したRealmファイルを開く際に、間違ったキーを指定したりキーを渡さなかった場合は、GetInstanceを呼ぶタイミングでRealmFileAccessErrorExceptionが発生します。

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

同期

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

Realm Mobile Platformを有効にする

Realm Mobile Platformを有効にするために特別な手順は必要ありません。NuGetのRealmパッケージにデフォルトで含まれています。

同期はWindows環境では利用できません。iOSまたはAndroid環境でのみ利用できます。

Userクラス

Realmユーザー(User)はRealm Object Serverの中心となるクラスです。Userクラスはユーザー名+パスワードによる認証に加え、SNSを使う方法などいくつかのサードパーティの認証方式をサポートしています。

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

  • 接続先のRealm ServerのUri
  • ユーザーを認証するための認証情報(Credentials)。認証情報は認証方式により変わります。(例: ユーザー名+パスワード、アクセストークンなど)

この2つのオブジェクトはUserオブジェクトを作成するために使われます。

Credentialオブジェクトの作成

サードパーティの認証情報を作成するためのドキュメントはRealm Object Server Authenticationにあります。

認証情報の種類は次のとおりです。

ユーザー名/パスワード
var credentials = Credentials.UsernamePassword(username, password, createUser: false);

UsernamePassword()メソッドの3番目の引数であるcreateUserはユーザーを新らしく作成するかどうかを示すフラグです。trueを指定できるのは作成する最初の1度だけです。ユーザーが作成済みですでに存在する場合はfalseを指定しなければなりません。

またはすべてのユーザーをあらかじめWebの管理者ダッシュボードから作成しておき、createUserメソッドには常にfalseを渡すという運用も可能です。

Google
var token = "..."; // a string representation of a token obtained by Google Login API
var credentials = Credentials.Google(token);
Facebook
var token = "..."; // a string representation of a token obtained by Facebook Login API
var credentials = Credentials.Facebook(token);

ログイン

ユーザーを作成するために必要な情報が揃ったなら、Realm Object Serverにログインできます。

var authURL = new Uri("http://my.realm-auth-server.com:9080");
var user = await User.LoginAsync(credentials, authURL);

ログアウト

同期されたRealmからログアウトするには次のようにします。

user.LogOut();

ログアウトすると同期は停止します。ログアウトしたユーザーは同期されたRealmファイルを開くことはできなくなります。

ユーザーの保存方法のカスタマイズ

デフォルトでは一度ログインしたユーザーは自動的にRealmによって保存されます。アプリケーションを起動するたびにログインする必要はありません。ユーザーを保存するかどうかはUser.ConfigurePersistenceを用いて変更できます。保存に関する挙動は3つのモードがあります。

  • Disabled: ユーザーオブジェクトは保存されません。自分でユーザーの認証情報を保存するか、アプリケーションを起動するたびにいつでもログインする必要があります。
  • NotEncrypted: ユーザーオブジェクトを暗号化せずに保存します。ユーザーの認証情報を守るために、このオプションを使用することは推奨しません。
  • Encrypted: ユーザーの認証情報を暗号化して保存します。このオプションを選択する場合は正しくencryptionKeyを指定する必要があります。

iOS環境ではデフォルトでEncryptedが指定されており、指定されたencryptionKeyを用いてKeychainび暗号化してユーザーを保存します。AndroidではデフォルトではNotEncryptedが指定されています。AndroidKeyStoreのAPIはAPIレベルが18以上でなければ使用できないためです。可能ならばAndroid環境ではEncryptedモードに変更しencryptionKeyをアプリケーションごとに指定することを推奨します。

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

通常の組み込みデータベースとしてのRealmでは、Realmの設定を変更するためにRealmConfigurationを使用します。Realm Mobile PlatformではSyncConfigurationを代わりに利用します。

設定オブジェクトはユーザーと同期サーバーのURLに関連づけられています。同期サーバーのURLはチルダ(“~”)を含むことがあります。チルダは各ユーザーのユニークIDに透過的に変換されます。この仕組みは、ユーザーごとに別のRealm URLを指定する場合に非常に便利です。

同期されたRealmファイル保存される場所はフレームワークによって管理されています。保存場所は必要に応じて変更できます。

var serverURL = new Uri("realm://my.realm-server.com:9080/~/default");
var configuration = new SyncConfiguration(user, serverURL);

ユーザーを保存する機能を有効にしている場合は、User.Currentを用いてログイン済みのユーザーを取得できます。ログイン済みユーザーが存在しない、またはすべてのユーザーがログアウトしていたならnullが返されます。複数のユーザーがログイン中の場合はRealmExceptionが発生します。

var user = User.Current;

複数のユーザーがログイン中であると考えられる場合は、User.AllLoggedInを用いてログイン中のすべてのユーザーのコレクションを取得できます。ログイン中のユーザーが存在しなければ空のコレクションが返ります。

var users = User.AllLoggedIn;

foreach (var user in users)
{
    // do something with the user
}

同期されたRealmを開く

SyncConfigurationを作成できたら、同期されたRealmインスタンスは通常のRealmと同じように作成できます。

var realm = Realm.GetInstance(configuration);

同期セッション

同期されたRealmとRealm Object Serverとの接続はSessionオブジェクトとして表されます。 セッションオブジェクトはrealm.GetSession()メソッドを用いて取得します。

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

同期の進捗を通知

セッションオブジェクトからsession.GetProgressObservable(direction, mode)を用いてIObservableオブジェクトを取得し、監視することでアプリとObject Serverの同期(アップロード、ダウンロード両方)の進捗状態を監視できます。

同期の進捗を通知するコールバックは同期システムによって継続的に呼び出されます。複数のコールバックを同時に登録でき、ブロックはアップロードとダウンロードの両方について設定することができます。コールバックにはSyncProgressオブジェクトが渡され、全体のバイト数と、そのうちの転送済みのバイト数が含まれています(全体のバイト数は、転送済みのバイト数と未転送のバイト数として渡されます)。

コールバックが登録されると、IDisposable型のトークンオブジェクトを返します。通知を有効にする間はこのオブジェクトを保持しておかなければなりません。通知を停止するにはトークンオブジェクトに対してDisposeメソッドを呼びます。

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

var token = session.GetProgressObservable(ProgressDirection.Download, ProgressMode.ReportIndefinitely)
                   .Subscribe(progress =>
                   {
                       if (progress.TransferredBytes < progress.TransferableBytes)
                       {
                           // Show progress indicator
                       }
                       else
                       {
                           // Hide the progress indicator
                       }
                   });
  • ForCurrentlyOutstandingWork - 登録されたときに、転送されるべきバイト数を取得し、その値を基準に進捗が通知されます。この通知は転送バイト数がその初期値に達するか超えると、自動的に通知を解除します。この種類の通知は、例えば初回のRealmファイルの同期が完了するまでの進捗状況の表示や、大きめのデータのアップロードの進捗表示に利用できます。
var token = session.GetProgressObservable(ProgressDirection.Download, ProgressMode.ForCurrentlyOutstandingWork)
                   .Subscribe(progress =>
                   {
                       var progressPercentage = progress.TransferredBytes / progress.TransferableBytes;
                       progressBar.SetValue(progressPercentage);

                       if (progressPercentage == 1)
                       {
                           progressBar.Hide();
                       }
                   });

ここで示しているコード例は、通知の登録を簡単にするためにObservableExtensions.Subscribe拡張メソッドを使用しています。Realmでは通知のコードをシンプルにするためにReactive Extensionsクラスライブラリを使用することを推奨しています。例えば、下記はさらに複雑な通知のシナリオを表しています。アップロードとダウンロードの両方を監視し、通知の回数を制限し、最終的にメインスレッドにディスパッチします。

var uploadProgress = session.GetProgressObservable(ProgressDirection.Upload, ProgressMode.ReportIndefinitely);
var downloadProgress = session.GetProgressObservable(ProgressDirection.Download, ProgressMode.ReportIndefinitely);

var token = uploadProgress.CombineLatest(downloadProgress, (upload, download) =>
                          {
                              return new
                              {
                                  TotalTransferred = upload.TransferredBytes + download.TransferredBytes,
                                  TotalTransferable = upload.TransferableBytes + download.TransferableBytes
                              };
                          })
                          .Throttle(TimeSpan.FromSeconds(0.1))
                          .ObserveOn(SynchronizationContext.Current)
                          .Subscribe(progress =>
                          {
                              if (progress.TotalTransferred < progress.TotalTransferable)
                              {
                                  // Show spinner
                              }
                              else
                              {
                                  // Hide spinner
                              }
                          });

アクセスコントロール

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

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

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

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

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

Management Realm

アクセス権限を変更はManagement Realmに変更内容を書き込むことで行います。 Management Realmは一般の同期されたRealmとまったく同じものです。しかし、Realm Object ServerはManagement Realmの変更を監視するように設計されています。 特定のRealmファイルに対するアクセス権限を変更するには、変更のためのオブジェクトをManagement Realmに追加します。すると、その変更がRealmファイルに対して適用されます。

特定のユーザーに対するManagement Realmオブジェクトを取得するには、user.GetManagementRealm()拡張メソッドを使用します。

var managementRealm = user.GetManagementRealm();

Modifying Permissions

Realmファイルに対してアクセス権限を変更するには、下記のようにIPermissionObjectオブジェクトをManagement Realmに書き込みます。現在は2つのワークフローをサポートしています。

PermissionChange

PermissionChangeオブジェクトを用いると、同期されたRealmのアクセス権を直接変更することができます。アクセス権を付与したいユーザーのIDが分かっている場合や、全ユーザーにアクセス権を付与したい場合にはこちらの方法が適しています。

var permissionChange = new PermissionChange(realmUrl,
                                            anotherUserId,
                                            mayRead: true,
                                            mayWrite: true,
                                            mayManage: false);

managementRealm.Write(() =>
{
    managementRealm.Add(permissionChange);
});

特定のユーザーが作成したすべてのRealmファイルのアクセス権限を変更するには、realmURL引数に*を指定します。 すべてのユーザーに対してアクセス権限を変更するには、userID引数に*を指定します。

mayReadmayWritemayManageプロパティに値を指定しなかった場合は、もしくはnullを指定した場合はreadwritemanageの状態は変更されません。このことは、例えばあるユーザーにread権限を付与し、かつ、そのユーザーがもともとwrite権限を持っていた場合はそのままにしたい、というときなどに活用できます。

managementRealm.Write(() =>
{
    foreach (var id in userIds)
    {
        var permissionChange = new PermissionChange(realmUrl, userId, mayRead: true);
        managementRealm.Add(permissionChange);
    }
});

上記のようにすると、usersに含まれるすべてのユーザーにread権限が付与されます。各ユーザーがもともとwrite権限もしくはmanage権限を持っていた場合はそのままになり、失われることはありません。

PermissionOffer/PermissionResponse

PermissionOfferPermissionOfferResponseはペアで使用し、あるユーザーがファイルの共有を別のユーザー(複数の場合もあります)に招待し、招待を受けたユーザーが共有を承認するというワークフローをサポートします。

var permissionOffer = new PermissionOffer(realmUrl,
                                          mayRead: true,
                                          mayWrite: true,
                                          mayManage: false,
                                          expiresAt: DateTimeOffset.UtcNow.AddDays(7));

managementRealm.Write(() =>
{
    managementRealm.Add(permissionOffer);
});

/* Wait for the offer to be processed */

var token = permissionOffer.Token;

/* Send token to the other user */

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

トークン(token)を受け取ったユーザーはトークンを使用して共有を開始できます。

var offerResponse = new PermissionOfferResponse(token);
var managementRealm = anotherUser.GetManagementRealm();
managementRealm.Write(() =>
{
    managementRealm.Add(offerResponse);
});

/* Wait for the offer to be processed */

var realmUrl = offerResponse.RealmUrl;

// Now we can open the shared realm:
var configuration = new SyncConfiguration(anotherUser, new Uri(realmUrl));
var offeredRealm = Realm.GetInstance(configuration);

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

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

サーバーから結果を受け取る

Object ServerがIPermissionObjectの操作を完了した際には、オブジェクトのStatusプロパティとStatusMessageプロパティに結果を示す値がセットされます。

アクセス権の変更の結果を通知するにはPropertyChangedを使用します。通知の仕組みについて詳しくはオブジェクトに対する通知をご覧ください。

下記はアクセス権の変更を監視する方法の例です。

permissionObject.PropertyChanged += (s, e) =>
{
    if (e.PropertyName == nameof(IPermissionObject.Status))
    {
        switch (permissionObject.Status)
        {
            case ManagementObjectStatus.NotProcessed:
                // Handle case
                break;
            case ManagementObjectStatus.Success:
                // Handle case
                break;
            case ManagementObjectStatus.Error:
                // Handle case
                break;
        }
    }
};

エラー処理

同期関連のAPIのうち、失敗する可能性のある操作については例外が発生するものがあります。例外はtry-catchブロックで捕捉できます。

合わせて、Session.Errorイベントを監視することを 強く推奨 します。グローバルな同期サブシステム、または特定のセッション(セッションは同期サーバーと関連したRealmを表します)により発生するエラーはこのハンドラによって通知されます。エラーば発生すると、エラーハンドラが呼び出されます。ハンドラにはErrorEventArgsパラメータが渡され、例外オブジェクトと、エラーが発生したSessionオブジェクトが含まれています。

Session.Error += (session, errorArgs) =>
{
    // handle error
};

エラーについて

Realm Mobile PlatformのエラーはSessionExceptionsとして表されます。標準の例外が持っているプロパティに加えて、ErrorCodeが使用できます。このプロパティにより、エラーの種類を強い型付けによって区別することができます。

Session.Error += (session, errorArgs) =>
{
    var sessionException = (SessionException)errorArgs.Exception;
    switch (sessionException.ErrorCode)
    {
        case ErrorCode.AccessTokenExpired:
        case ErrorCode.BadUserAuthentication:
            // Ask user for credentials
            break;
        case ErrorCode.PermissionDenied:
            // Tell the user they don't have permissions to work with that Realm
            break;
        case ErrorCode.Unknown:
            // Likely the app version is too old, prompt for update
            break;
        // ...
    }
};

クライアントリセット

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

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

クライアントリセットが要求されたかどうかは、Session.Errorオブジェクトのエラーハンドラにて通知されます。クライアントリセットのエラーはsessionException.ErrorCode.IsClientResetErrorをチェックするか、ClientResetExceptionにキャストできるかどうかで判断します。このオブジェクトには追加のBackupFilePathプロパティを持っています。このプロパティはクライアントリセットの処理中に作成されるバックアップコピーの保存先を示します。また、クライアントリセットの処理が開始したときに呼ばれるInitiateClientReset関数もあります。

この関数を自分で呼び出してクライアントリセットの処理を開始するには、関数を呼び出す前にすべてのRealmインスタンスは 解放されていなければなりません 。そうすることでクライアントリセットのプロセスが完了した際にはすぐにRealmをオープンし、同期を再開できます。

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

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

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

void CloseRealmSafely()
{
    // Safely dispose the realm instances, possibly notify the user
}

void SaveBackupRealmPath(string path)
{
    // Persist the location of the backup realm
}

void SetupErrorHandling()
{
    Session.Error += (session, errorArgs)
    {
        var sessionException = (SessionException)errorArgs.Exception;
        if (sessionException.ErrorCode.IsClientResetError())
        {
            var clientResetException = (ClientResetException)errorArgs.Exception;
            CloseRealmSafely();
            SaveBackupRealmPath(clientResetException.BackupFilePath);
            clientResetException.InitiateClientReset();
        }

        // Handle other errors
    }
}

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

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

同期されたRealmについてはマイグレーションは自動的に適用されます。ただし、いくつかの制限と注意事項があります。クラスやプロパティを追加するといった、追加による変更のみ適用されます。追加ではない変更がある場合は例外が発生します。

マイグレーションは必要に応じて自動的に実行されます。スキーマバージョンを設定したり、マイグレーション処理を記述する必要はありません。

コンフリクトの解決

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

既知の制限事項

Realm Xamarinは現在ベータ版であり、1.0のリリースに向けて、頻繁に昨日の追加や不具合の修正が行われています。ベータ版の間は以下の制限があります。

さらに網羅的に現在の制限を知りたい場合は、GitHub issuesをご覧ください。

これらの未実装の機能は正式版のリリースまでには実装されます。

一般的な制限事項

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

  1. クラス名は57文字が上限です。UTF-8の文字をサポートしています。超えている場合は例外が発生します。
  2. プロパティ名は63文字が上限です。UTF-8の文字をサポートしています。超えている場合は例外が発生します。
  3. iOSにおける制限: 各Realmファイルのサイズは、アプリケーションごとにに割り当てられるメモリサイズを超えてはいけません。割り当てられるメモリサイズは、デバイスごとに異なり、実行時のメモリの断片化にも依存します。(詳しくは、rdar://17119975 をご覧ください)それ以上のデータを保存される場合は、Realmファイルを複数に分割してください。

スレッド

Realmファイルは複数のスレッドから同時にアクセスできますが、RealmインスタンスやRealmオブジェクト、クエリ、検索結果のオブジェクトをスレッド間で受け渡すことはできません。Realmのスレッドについて詳しくはマルチスレッドセクションをご覧ください。.

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

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

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

この余分な領域は、最終的には再利用されるか消去されます。

空き領域をすぐに消去したい場合は、コンパクションのセクションを参照してください。

Windowsデスクトップ環境における制限事項

Realm Xamarinがサポートするすべてのプラットフォーム間で機能の等価性をを確保することは簡単ではありません。現在はWindows環境に特有のいくつかの制限があります。近い将来この制限は修正される予定です。

  1. 同期はまだ実装されていません。WindowsアプリケーションでRealmパッケージをNuGetに指定すると、ビルド時にエラーが発生します。その代わりに、Realm.Databaseパッケージをインストールして、オフライン対応バイナリだけをインストールする必要があります。
  2. 暗号化は未サポートです。RealmConfigurationEncryptionKeyにNull出ない値を’指定してRealm.GetInstanceを呼び出すと、実行時例外が発生します。
  3. 多くの異なるプロセスが同じRealmファイルを開いていると、一部の変更通知が受け取れないことがあります。

FAQ

一般的な質問

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

はい。C++で書かれた内部ストレージエンジン、および各言語のSDKは完全にオープンソースソフトウェアとしてApache 2.0ライセンスの元に公開されています。 拡張コンポーネントであるRealm Platformのソースコードは非公開ですが、このコンポーネントはRealmを組み込みデータベースとして使う場合には必須ではありません。

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

Realmは匿名の統計データを収集しています。統計データの送信は、Realmが[RealmObject](/docs/dotnet/1.0.3/api/reference/Realms.RealmObject.html)クラスをWeavingする際に行われます。送信するデータに個人を特定する情報は一切含まれません。データにはRealmのバージョン、(iOS、OS Xなどの)プラットフォーム、プログラミング言語、などの情報が含まれ、それはRealmの製品の向上にのみ利用されます。統計データの送信は、リリースビルド、またはデバイス上で動作している時には行われません。あくまでも、ビルド時にFodyがWeavingを実行する時だけ送信されます。実際にどのようなデータを収集しているのかは、ソースコードから確認していただけます。

RealmはPCL(ポータブル・クラス・ライブラリ)として動作しますか?

実行ファイルを作るにはiOS、AndroidまたはWin32のそれぞれのバージョンのRealmを利用する必要があります。しかし、Realm APIに対してコンパイルできるPCLを提供しています。これは「NuGet Bait and Switch Trick」という記事で紹介されているテクニックです。PCLプロジェクトにNuGetを用いてRealmを追加し、それを実行ファイルに追加すれば動作します。

任意の.NETプラットフォームでRealmを使用できるわけではありません 。RealmネイティブのC++ coreに依存しているので、コアを各プラットフォームに移植する必要があるからです。

トラブルシューティング

クラッシュレポート

私たちは開発者の方にクラッシュレポーターを使用していただくことを推奨しています。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. クラッシュログやスタックトレース。上述のクラッシュレポートの項目も参考にしてください。

クラスにプロパティが1つも無い(No properties in class)という実行時例外が起こる

“No properties in class, has linker stripped it?”というメッセージのSystem.InvalidOperationExceptionが発生することがあります。

バージョン0.77.2以降では、もう少し詳しい情報が表示されます。

判明している原因は2つあります。

  1. Fodyに何か問題が発生していてWeavingされたRealmObjectsが1つも無い
  2. RealmObjectsのプロパティがStripされてしまっていてRealmが空になっている

最初のケースに関しては例外はRealmSchemaからスローされます。詳しくはWeavingに失敗したときのセクションをご覧ください。

2番目のケースに関しては例外はObjectSchemaからスローされます。 Linker BeahviourLink All に設定していてクラス宣言に[Preserve(AllMembers = true)]属性が無い場合に問題が起こることがあると報告されています。リンカーはコード内で明示的に参照されるメンバーのみを保持します。つまり、永続化されるがどこでも参照されないプロパティがある場合、そのスキーマが削除されてデータベースの不一致が発生する可能性があります。 デフォルトの挙動では、Realmはすべてのアセンブリ内のRealmObjectサブクラスの すべて に対するするスキーマを構築します。これは 遅延 されるので、最初にGetInstance()を呼び出すまでは起こりません。これは実行するごとに1回だけ実行され、結果をメモリにキャッシュします。

特定のモデルクラスだけをRealmに保存したい場合は、ObjectClassesでサブセットを指定する方法でGetInstance(myConf)を使ってRealmを作成します。このようにすることで、すべてのクラスに対してスキーマ構築はされなくなるので、例外を回避できます。

それ以外の場合は、未使用のクラスがある場合はPreserve属性を追加してください。

Fody: キャッチされなかった例外が発生する

このビルどエラーは既存のプロジェクトに新しくRealmObjectのサブクラスを追加するだけで容易に発生します。

BuildまたはRunを選択するだけでは、Fody Weaverを使用してビルドされません。

Rebuildを選択すると、正しくビルドできます。

Weavingに失敗したとき

ビルドログにクラスがWeavingされなかったという旨の警告メッセージが表示されることがあります。これはWeavingを行うライブラリであるFodyが正しくインストールされていないことを示しています。

まず最初に、FodyWeavers.xmlファイルにRealmWeaverの記述があるかどうか確認してください。

Fodyのインストール時にはソリューションのディレクトリ(通常はプロジェクトディレクトリと同じ階層にあります)のToolsディレクトリにRealmWeaver.Fody.dllがコピーされます。このファイルはどのプロジェクトともリンクされてはいませんが、FodyがDLLをロードするためにはこの場所に存在する必要があります。

Visual Studio 2015とNuGetパッケージマネージャの3.2より古いバージョンの組み合わせで使っていると、Fodyのインストールが失敗することがあります。確認するためには、テキストエディタを用いて、.csprojファイルを開き、Fody.targetsをインポートしている行を見ます。その行は下記のようになっています。

<Import Project="..\packages\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets"
    Condition="Exists('..\packages\Fody.1.29.3\build\portable-net+sl+win+wpa+wp\Fody.targets')" />

最新のバージョンのNuGetパッケージマネージャにアップグレードすると、この問題は解決します。

これでうまくいかなければ、おそらくFodyとMicrosoft.Bcl.Build.targetsに問題があります。.csprojファイルから次の行を削除すると、直ることがあります。

<Import Project="..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets" Condition="Exists('..\..\packages\Microsoft.Bcl.Build.1.0.21\build\Microsoft.Bcl.Build.targets')" />

このことについてより詳しくはこちらのStackOverflowの回答をご覧ください。

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

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を見てサービスの稼働状況を確認し、 障害が解決された後で再度やり直してください。