This is not the current version. View the latest documentation

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

// 通常の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 RealmList<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(() =>
{
    var mydog = realm.CreateObject<Dog>();
    mydog.Name = "Rex";
    mydog.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 5.00.0以上
  • Xamarin.iOS 9.8.0.244以上、iOS 7以上、native UIとXamarin Formsの両方をサポート
  • Xamarin.Android 6.1.0.37以上、API level 10以上、native UIとXamarin Formsの両方をサポート
  • Xamarin.Macは未サポートです

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

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

インストール

  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を生成する必要があることです。

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

// 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 RealmList<Dog> Dogs { get; }
}

対応しているデータ型

Realmは符号なし整数型以外のプリミティブ型(boolcharbyteshortintlongfloatdouble)とstring、そしてDateTimeOffset型をサポートしています。Null許容型も同様にサポートしています。

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

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

RealmList.NETの標準ライブラリのIListのインターフェースに準拠しています。

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

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

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

public class Person : RealmObject{}

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

var jim = realm.CreateObject<Person>()
var rex = realm.CreateObject<Dog>();
rex.Name = "Rex";
rex.Owner = jim;

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

rex.Owner = null;

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

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

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

例えば、PersonクラスがDogオブジェクトを複数持てるように、1対多の関連として“Dogs”を追加するとすると、RealmList<Dog>をプロパティとして定義するだけです。

public class Dog : RealmObject
{
}

public class Person : RealmObject
{
    // ... other property declarations
    public RealmList<Dog> Dogs { get; }
}

RealmListIListと同じようにアクセスしたり、オブジェクトを追加することができます。

jim.Dogs.Add(rex);
Assert(jim.Dogs.Count == 1);  // nobody but rex

Null許容型のプロパティ

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

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

保存しないプロパティ

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

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

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

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 RealmList<Dog> Dogs { get; } } `

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

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

Dog myDog;
realm.Write(() =>
{
    var mydog = realm.CreateObject<Dog>();
    mydog.Name = "Fido";
    mydog.Age = 1;
});

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

Console.WriteLine($"age of my dog: {myDog.age}") // => 2

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

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

ObjectId属性

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

charと整数型、および、文字列型のプロパティのみ、オブジェクトIDとして指定できkます。

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

もし、伝統的なデータベースソフトウェアの知識をご存じなら、オブジェクトIDはSQLにおける プライマリキー と同じように考えることもできます。

データ構造のモデリングという観点からは、オブジェクトIDは永続化されたオブジェクトのポインタと見なすこともできます。異なるスレッドで同じオブジェクトを高速に取得するためにオブジェクトIDを使用できます。

複数のプロパティに[ObjectId]属性を付加した場合は未定義の動作を引き起こします。おそらく実行時にエラーが発生するか、どれか1つだけが有効になります。

public class Person : RealmObject
{
    [ObjectId]
    public string SSN { get; set; }
    [Indexed]
    public string Name { get; set; }
    public RealmList<Dog> Dogs { get; }
}

保存しないプロパティ

[Ignored]属性を用いて、特定のプロパティをRealmに保存せずに、単なる普通のC#のオブジェクトのプロパティとして扱うことができます。

または、プロパティにsetterもしくはgetterメソッドと定義した場合も、自動的にそのプロパティは保存しないプロパティとして扱われます。

モデルクラスの継承

Realm Xamarinではモデルクラスをさらに継承することはできません。ジェネリックメソッドのCreateObjectRealmObjectの直接のサブクラスに対してのみ、正しく動作します。

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

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

  1. RealmResultsクエリを用いて取得したオブジェクトを表します。
  2. RealmList、モデルオブジェクトにて1対多の関係を表すためのクラスです。

どちらのクラスもIEnumerableインターフェースに準拠しています。そのため、遅延評価をサポートしています。つまり、コレクションのサイズを取得したとしても、実際の要素のオブジェクトがメモリにロードされるのはコレクションをループしたときまで遅延されます。

書き込み

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

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

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

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

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

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.CreateObject<>()メソッドを用いてモデルのインスタンスを作成します。

realm.Write(() =>
{
    var mydog = realm.CreateObject<Dog>();  // 即座にRealmによって管理(保存)されます
    mydog.Name = "Rex";
    myDog.Age = 10;
});

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

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

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

オブジェクトの更新

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

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

オブジェクトの削除

オブジェクトを削除するには、トランザクションの中で削除したいオブジェクトを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構文を実装しています。

まずAllメソッドに型を指定して、その型の全てのオブジェクトを取得します。これがもっとも基本的なクエリになります。それに対して、Whereやその他の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;

どちらの構文を利用した場合でも結果はRealmResultsが返ります。このコレクションオブジェクトは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という変数はRealmResultsのインスタンスになります。IQueryableを実装しています。”John”または”Peter”というファーストネームを持つオブジェクトが格納されています。

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

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

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

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

int i = 0;
foreach (var pers in johnsAndPeters) {        // iterate query
    Assert.That(peopleList[i++], Is.EqualTo(pers));  // check iterator against earlier list
}

論理演算子

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

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

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

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

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

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

var ap = realm.All<Person> ();  // 指定した型のすべてのオブジェクトを取得
var scorers = ap.Where(p => p.Score > 35);  // 条件に当てはまるオブジェクトを絞り込む
var scorerDoe = scorers.Where(p => p.LastName == "Doe");  // AND条件を書いたのと同様です

並べ替え

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

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

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

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

// sorting by two clauses and THEN using ToList to compare in an assertion
// (from a unit test that has duplicate first names)
var sortAD = realm.All<Person>().Where(p => p.IsInteresting).
    OrderBy(p => p.FirstName).
    ThenByDescending(p => p.Latitude).ToList();
var sortADname = sortAD.Select(p => p.FirstName);
Assert.That(sortADname, Is.EqualTo( new [] {"John", "John", "Peter"}) );

クエリの連鎖

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

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

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

Realmについて

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

デフォルトRealm

すでにお気づきだと思いますが、これまでRealm.GetInstance(string optionalPath)を呼ぶことで、変数realmを初期化してきました。このStaticコンストラクタは、Environment.SpecialFolder.Personalが指すパスに作られたdefault.realmという名前のファイルを指すRealmインスタンスをスレッドごとに返します。

Realmインスタンスはスレッドシングルトンであることに注意してください。つまり、このStaticコンストラクタはスレッドごとに同じインスタンスを返します。

Realm Configuration

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

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

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

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

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

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

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

スコープ外になる前にクローズするためにRealm.Close()を呼ぶことができます。Realm.Close()はいつでも、何度でも安全に呼ぶことができます。

Realmファイルの探し方

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

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

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

  • .realm.lock - Realmファイル開く際の競合を防ぐためのロックとして使われます。
  • .realm.log_a, .realm.log_b - トランザクションログを記録するファイルです。
  • .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のオブジェクトはすべて一般的なオブジェクトと同じように扱え、並行処理やマルチスレッドについて気にする必要はありません。 Realmにアクセスするためにロックや排他処理を考える必要はありません(たとえRealmが他のスレッドから同時に更新されるとしても)。そして、データの変更が起こるのはトランザクションブロックで囲まれた範囲だけと考えられます。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

通知

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

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

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

マイグレーション

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

Realmは単純なプロパティやクラスの追加・削除については自動的にマイグレーションを実行します。データの統合など、複雑な場合では適切なマイグレーション処理が必要になります。マイグレーション処理のAPIはまだサポートされていませんが、近い将来、可能になります。

暗号化

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

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

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

  • 複数の関連を一度に追加できるようにリストの値をRealmListプロパティに代入する
  • 非同期クエリ
  • バイナリデータの保存(例: 画像を保存するなど)
  • 関連のカスケード削除
  • コレクションに対する通知
  • デフォルト値 – 標準的な方法はWeavingと互換性がないため
  • オブジェクトの全削除
  • ダイナミックRealm
  • RealmListではなくIListを定義に使えるようにする
  • マイグレーション
  • さらなるLINQクエリのサポート
  • 関連に対する検索
  • ObjectIdを用いてObjectを更新する

一般的な制限事項

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

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

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

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

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

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

FAQ

一般的な質問

Realmをプロダクション環境で使うことはできますか?

Realmは、2012年から商用のプロダクトで利用されています。これはXamarin C#版の提供は2015年の12月からですが、すべてのRealmプロダクトは同じC++のデータベースエンジンを使用していることによります。

現在のRealmを利用する場合は、RealmのC#のAPIが、コミュニティのフィードバックを受けて変わりうるものだとしてお使いください。 機能追加、不具合の修正も同様に考えてください。

Realmを使うために料金を支払う必要がありますか?

いいえ、Realmは完全に無料です。商用の製品で利用することも可能です。

Realmはどのようなビジネスプランなのですか?

すでにエンタープライズ向けの商品の販売や、周辺サービスによって収益を得ています。もし現在リリースされているrealm-cocoa以上の機能が必要であれば、いつでもメールで、お気軽にご連絡ください。

また私たちのビジネスとは関係なく、realm-dotnetはオープンソースのまま開発を続け、Apache License 2.0として公開し続けます。

“core”という文字列をコードの中で見たのですが、これは何ですか?

「コア」は、Realmが利用している内部ストレージエンジンの名称です。現在、コアのソースコードはオープンソースではありませんが、まもなくApache License 2.0にて公開する予定です。バイナリについては、Realm Core (TightDB) Binary Licenseで利用可能です。

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

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

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

実行ファイルを作るにはiOSまたはAndroidのそれぞれのバージョンのRealmを利用する必要があります。しかし、Realm APIに対してコンパイルできるPCLを提供しています。これは「NuGet Bait and Switch Trick」という記事で紹介されているテクニックです。PCLプロジェクトにNuGetを用いて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 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を見てサービスの稼働状況を確認し、 障害が解決された後で再度やり直してください。

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パッケージマネージャにアップグレードすると、この問題は解決します。