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 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(() =>
{
var myDog = new Dog { Name = "Rex", Age = 1 };
realm.Manage(myDog);
});
// 検索結果は自動的にリアルタイムに更新されます
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にあります。 ナイトリービルドはMyGet経由で利用できます。
必要条件
下記の開発環境をサポートしています:
- 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の両方をサポート
- 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サポート
いくつかの命令セットに制限があるので、armeabi
ABI設定を サポートしていません 。
Xamarinプロジェクトを新規作成する際のデフォルトテンプレートではデバッグビルドではすべてのABI設定が有効になっていますが、リリースビルドではarmeabi
だけが有効になっています。 リリースビルドではこの設定を変更する必要があります 。
ABI設定を正しく行っていない場合、特定のアーキテクチャのデバイスでSystem.TypeInitializationException
が発生することがあります。たとえば、Galaxy Tab S2などの64 bitデバイスにデプロイすると、armeabi
とarmeabi-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
を指定します。
- ソリューションペインのプロジェクトの下にある”Packages”の歯車の形のボタンをクリックし、”Add Packages…“を選択します。
- 検索フィールドに”Realm”と入力します。
- 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>
- プロジェクトにて、ソリューションからTools - NuGet Package Manager - Manage Packagesと選択します。
- インストール可能なパッケージの一覧から、Realmパッケージを選択します。
- 右側にプロジェクトがチェックされていることと、インストールボタンが押せることを確認します。
- インストールボタンを押します。
- ダイアログが表示されたら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のバージョンです。
Tools > Generate demo database と選択すると、サンプルデータを含むテスト用のRealmデータベースを作ることができます。
開発中のアプリのRealmファイルがどの場所に格納されているかは、このStackOverflowの回答が参考になります。
Realm BrowserはMac 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は符号なし整数型以外のプリミティブ型(bool
、char
、byte
、short
、int
、long
、float
、double
)とstring
、そしてDateTimeOffset
型をサポートしています。Null許容型も同様にサポートしています。詳しくはNull許容型のプロパティセクションで説明しています。
Date型
日付を表す型としてはDateTime
型ではなく標準のDateTimeOffset
型を採用しています。
このことはMicrosoftによって曖昧さがあるDateTime
よりも推奨されていることに基づいています。
実際の値は100ナノ秒の精度で保存されます。
タイムゾーンを指定してDateTimeOffset
を保存できますが、RealmにはUTCの値が保存されます。他のプラットフォームとの互換性のために曖昧さのない表現を採用しています。タイムゾーン指定子がなくなるので、UtcTicks
の代わりに Ticks
を使って値を比較すると正しくないことがあります。
リレーションシップ(関連)
RealmObjectはプロパティにRealmObjectやIListを使用して、他のオブジェクトとの関連を定義することができます。
RealmList.NETの標準ライブラリのIList
のインターフェースに準拠しています。プロパティはIList
型として定義します。そのプロパティにアクセスした際にはRealmList
型のオブジェクトが返ります。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 = new Person { Name = “Jim” }; realm.Manage(jim);
var rex = new Dog { Owner = jim };
realm.Manage(rex); });
関連を解除するには単にnull
を代入します。
rex.Owner = null;
RealmObjectをプロパティとして持つとき、ネストされたプロパティには通常のオブジェクトのプロパティと同じ文法でアクセスできます。上記の例では、rex.Owner.Address.Country
とすると、Realmはネストしたオブジェクトを必要に応じて自動的にフェッチします。
1対多のリレーションシップ
1対多の関連を定義するにはIList
をプロパティとして定義します。このプロパティにアクセスすると、空のRealmListか、単一のRealmObject型を要素に持つRealmListが返ります。
例えば、Person
クラスがDog
オブジェクトを複数持てるように、1対多の関連として“Dogs”を追加するとすると、IList<Dog>
をプロパティとして定義するだけです。RealmListオブジェクトを自分で生成することはできません。 – Realm.CreateObject
が自動的にオブジェクトを生成します。関連のオブジェクトについては要素のオブジェクトを追加または削除する操作のみ行うことができます。
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
Null許容型のプロパティ
string
、byte[]
や関連に用いるRealmObjectのような参照型はnull値をとることができます。
int?
のようなNull許容型やNull許容型のDateTimeOffset?
も完全にサポートしています。
プロパティの永続性をコントロールする
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]
属性をプロパティの定義に追加します。 (例)
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.Manage(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.ObjectForPrimaryKey
を使ってオブジェクトをすばやく取得することができます。これは、LINQを使用するよりもより効率的なインデックスを使用したクエリが使われます。文字列、文字または整数キーに対応するためにそれぞれのオーバーロードが定義されています。
public class Person : RealmObject
{
[PrimaryKey]
public string SSN { get; set; }
public string Name { get; set; }
public IList<Dog> Dogs { get; }
}
var objByPK = _realm.ObjectForPrimaryKey<Person>("457-55-5462");
保存しないプロパティ
[Ignored]
属性を用いて、特定のプロパティをRealmに保存せずに、単なる普通のC#のオブジェクトのプロパティとして扱うことができます。 または、プロパティにsetterもしくはgetterメソッドを定義した場合も、自動的にそのプロパティは保存しないプロパティとして扱われます。
モデルクラスの継承
Realm Xamarinではモデルクラスをさらに継承することはできません。RealmObjectの直接のサブクラス以外に継承されたクラスがあるとWeavingに失敗します。
コレクションクラスについて
RealmにはRealmオブジェクトの集合を扱うためのクラスが複数あります。それらを総称して”Realmコレクションクラス”と呼びます。
- RealmResults、クエリを用いて取得したオブジェクトを表します。
- 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の保存するにはRealm.Manage()
メソッドを使用します。保存されたインスタンスは自動更新されるようになります。
realm.Write(() =>
{
var myDog = new Dog();
myDog.Name = "Rex";
myDog.Age = 10;
realm.Manage(myDog);
});
別の方法として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演算子を繋げて、結果をフィルタすることができます。
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;
どちらの構文を利用した場合でも結果は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
分を用いてループすることもできます。
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>()
メソッドを使用します。指定したすべてのモデルクラスを保持したRealmResults
インスタンスが返ります。上記の例ではPerson
クラスのオブジェクトすべてが返ります。
さらに取得したオブジェクトを絞り込むために重ねてLINQクエリを適用することができます。遅延実行されるので、RealmResults
をループしたりしない限りは何のオーバーヘッドもありません。
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クエリのようにOrderBy
、OrderByDescending
、ThenBy
、ThenByDescending
を用いて、複数レベルの並び順をRealmResults
に対して指定することができます。
並べ替えは内部クエリエンジンにて非常に効率良く実行されます。並べ替えを実行するためにすべてのオブジェクトをロードすることはありません。また、単に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や実装の都合により、クエリの実行結果の一部分だけが必要だったとします。そのときは、単にRealmResults
オブジェクトを用いて、必要な要素にだけアクセスすれば良いのです。
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オブジェクトを一度作成したら後からパスの値を変えることはできません。)
- 絶対パスを指定して、ファイルパスを完全に変更する。
- 相対パスでディレクトリを指定して、Realmファイルを標準のパスの下層に作成する。
- ファイル名だけを指定して、保存場所は変えずにファイル名だけを変更する。
RealmConfiguration
オブジェクトではでは、スキーマバージョンを指定できます。 詳細については、マイグレーションのセクションをご覧ください。 開発中に限り、利便性のために既存のファイルと現在のスキーマが一致しないとき(マイグレーションが必要になるとき)に自動的に既存のファイルを削除し、新しいスキーマで作り直す機能があります。その機能を有効にするにはShouldDeleteIfMigrationNeeded
プロパティをtrue
に設定します。すると、Realm.GetInstance()
はスキーマが既存のファイルと一致しない場合は既存のファイルを自動的に削除します。リリースする前にモデルの構造を簡単に試行錯誤できます。リリースするアプリではこのプロパティを 有効にしないことを推奨します 。 意図せずデータが消えてしまうという事態を避けるために#if DEBUG
セクション内に設定することも良い方法です。
同じConfigurationオブジェクトをRealmをオープンする際に渡すことで、すべてのRealmを同じ設定で使うことができます。この方法は、複数のスレッドで同じRealmを扱うもっとも一般的な方法です。
Configurationオブジェクトを渡して回る代わりに、デフォルトConfigurationをオーバーライドすることで、すべて同じ設定を適用することもできます。
RealmインスタンスはConfiguration単位、スレッド単位のシングルトンであることに注意してください。つまり、Realm.GetInstance()
は、同じConfiguration、かつ同じスレッド上で呼び出されるなら同じインスタンスを返します。
Realmインスタンスをクローズする
Realm
はネイティブメモリとファイルディスクリプタの開放を行うためにIDisposable
を実装しています。そのため、インスタンスがスコープの外に出たときに、自動的にクローズされます。
スコープ外になる前に自分でRealm.Close()
を呼んでクローズすることもできます。同じファイルを参照している別のRealmインスタンスも合わせてクローズされることに注意してください。
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のオブジェクトはすべて一般的なオブジェクトと同じように扱え、並行処理やマルチスレッドについて気にする必要はありません。 Realmにアクセスするためにロックや排他処理を考える必要はありません(たとえRealmが他のスレッドから同時に更新されるとしても)。そして、データの変更が起こるのはトランザクションブロックで囲まれた範囲だけと考えられます。
Realmは並行処理を簡単に扱えるようにするために、それぞれのスレッドで常に一貫性を保ったデータを返します。数多くのスレッドからRealmを同時に操作したとしても、それぞれのスレッドごとにスナップショットのデータが返され、不整合な状態が見えることがありません。
1つだけ注意しなければならないことは、複数のスレッドをまたいで同じ Realmインスタンス を共有することはできないということです。もし、複数のスレッドで同じオブジェクトにアクセスする必要がある場合は、それぞれのスレッドでRealmインスタンスを取得する必要があります。(そうでなければデータが不整合に見える可能性があります。)
他のスレッドから更新されたデータを反映するには
メインスレッド(またはランループ/Looperスレッドを持つサブスレッド)では、ランループが回るごとに自動的に他のスレッドから更新されたデータが反映されます。それ以外のタイミングでは、その時点のスナップショットのデータが返され、他のスレッドでデータが更新されているかどうかを気にすることなく、常に一貫性のあるデータが見えることになります。
それぞれのスレッドで最初にRealmファイルをオープンしたとき、Realmの状態は最後のコミットが成功した状態にあります。そしてそれは次の更新が反映されるまでそのままです。 Realmはランループが回るたびに自動的に最新のデータに更新されます。 もしスレッドがランループを持っていない(一般的なバックグラウンドスレッド)場合は、最新のデータを反映するためにRealm.Refresh()
メソッドを自分で呼ぶ必要があります。
また、トランザクションがコミットされたときも最新のデータが反映されます(Transaction.Commit()
)。
定期的に行われる最新データの反映が失敗すると、トランザクション履歴が”Pinned”になり、ディスク領域の再利用を妨げます。それはファイルサイズの肥大を招くことがあります。この現象について詳しくは現バージョンにおける制限事項をご覧ください。
スレッド間でオブジェクトを受け渡す
永続化されたRealm
、RealmObject
、RealmResults
、またはRealmList
のインスタンスは、生成されたスレッド内でなければ利用することができません。 別のスレッドで利用されると例外が発生します。これはRealmがトランザクションを分離するための必要な仕様です。
スレッド間をまたいでオブジェクトを受け渡すための方法を紹介します。例えば、PrimaryKey
を用いて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インスタンスに対して通知を送ります。
realm.RealmChanged += () =>
{
// Update UI
}
Objectに対する通知
RealmObjectクラスをINotifyPropertyChangedインターフェースに対応させることにより、プロパティの変更を監視できます。プロパティに変更があるたびにPropertyChangedEventメソッドが呼ばれるようになります。
public class Account : RealmObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public long Balance { get; set; }
}
上記のように書くと、PropertyChangedEventイベントをこのクラスに対して監視できるようになります。Balance
プロパティに変更があるたびにイベントハンドラが呼び出されます。
var realm = Realm.GetInstance ();
realm.Write(() => {
var acc = realm.CreateObject<Account>();
acc.PropertyChanged += (sender, e) => { Debug.WriteLine("New value set for " + e.PropertyName); };
acc.Balance = 10; // => "New value set for Balance"
});
これだけでも便利な機能ですが、さらにXamarin.Formsを使ったデータバインディングも可能です。詳細は、XamarinドキュメントのデータバインディングからMVVMへを参照してください。
RealmResultに対する通知
RealmResultは自動更新されます。常に最新の状態に自動的にアップデートされるということです。アクセスしたときには常に最新の変更が反映された値が取得されます。
RealmResultの変更を監視するにはRealmResult.SubscribeForNotifications()
メソッドを使用します。さらにコールバックメソッドでは追加・削除・変更された内容の詳細を知ることができます。
realm.All<Person>().SubscribeForNotifications ((sender, changes, error) =>
{
// Access changes.InsertedIndices, changes.DeletedIndices and changes.ModifiedIndices
});
XAMLデータバインディングをRealmResultに使用する場合は、RealmResult.ToNotifyCollectionChanged()
拡張メソッドを使用します。これは、INotifyCollectionChangedを実装したバージョンのRealmResultを提供します。例えばXamarin Formsにプラグインされます。これを使用する方法については、Xamarin.FormsとRealmを使用したクロスプラットフォーム開発というブログ記事を参照してください。
マイグレーション
データベースを使ってる場合、時間が経つにつれ、データモデルは変更されていくものです。 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
型のプロパティ、OldRealm
とNewRealm
を持ちます。これらを使って古いデータモデルから新しいデータモデルにデータをコピーする処理を記述します。
クラスやプロパティの名前を変更した場合も、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 Xamarinは現在ベータ版であり、1.0のリリースに向けて、頻繁に昨日の追加や不具合の修正が行われています。ベータ版の間は以下の制限があります。
さらに網羅的に現在の制限を知りたい場合は、GitHub issuesをご覧ください。
これらの未実装の機能は正式版のリリースまでには実装されます。
- 非同期クエリ
- 関連のカスケード削除(ワークアラウンドとしてこちらのStackOverflowの回答が参考になります)
- コレクションに対する通知
- デフォルト値 – 標準的な方法はWeavingと互換性がないため
- さらなるLINQクエリのサポート - 詳しくはLINQサポートをご覧ください。
- 関連に対する検索
PrimaryKey
を用いてRealmObject
を更新する
一般的な制限事項
Realmは、柔軟性とパフォーマンスのバランスをうまく保つため、保存するデータに対していくつかの制限事項があります。
- クラス名は57文字が上限です。UTF-8の文字をサポートしています。超えている場合は例外が発生します。
- プロパティ名は63文字が上限です。UTF-8の文字をサポートしています。超えている場合は例外が発生します。
- iOSにおける制限: 各Realmファイルのサイズは、アプリケーションごとにに割り当てられるメモリサイズを超えてはいけません。割り当てられるメモリサイズは、デバイスごとに異なり、実行時のメモリの断片化にも依存します。(詳しくは、rdar://17119975 をご覧ください)それ以上のデータを保存される場合は、Realmファイルを複数に分割してください。
ファイルサイズと中間データについて
SQLiteなどにデータを保存した時より、ディスクの使用容量が少なくなることを期待されることかと思います。 Realmファイルの容量が考えているよりも大きい場合はRealmObjectは古い履歴データを残している可能性があります。
データの一貫性を保つために、Realmは最新のデータにアクセスしたときのみ履歴をアップデートします。 このことは、別のスレッドが多くのデータを長い時間をかけて書き込んでいる最中にデータを読み出そうとした場合、履歴はアップデートされずに古いデータを読み出すことになります。結果として、履歴の中間データが増加していくことになります。
この余分な領域は、最終的には再利用されるか消去されます。
FAQ
一般的な質問
Realmをプロダクション環境で使うことはできますか?
Realmは、2012年から商用のプロダクトで利用されています。これはXamarin C#版の提供は2015年の12月からですが、すべてのRealmプロダクトは同じC++のデータベースエンジンを使用していることによります。
現在のRealmを利用する場合は、RealmのC#のAPIが、コミュニティのフィードバックを受けて変わりうるものだとしてお使いください。 機能追加、不具合の修正も同様に考えてください。
Is Realm open source?
はい。C++で書かれた内部ストレージエンジン、および各言語のSDKは完全にオープンソースソフトウェアとしてApache 2.0ライセンスの元に公開されています。 拡張コンポーネントであるRealm Platformのソースコードは非公開ですが、このコンポーネントはRealmを組み込みデータベースとして使う場合には必須ではありません。
アプリケーションを起動したときに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を追加し、それを実行ファイルに追加すれば動作します。
任意の.NETプラットフォームでRealmを使用できるわけではありません 。RealmネイティブのC++ coreに依存しているので、コアを各プラットフォームに移植する必要があるからです。
トラブルシューティング
クラッシュレポート
私たちは開発者の方にクラッシュレポーターを使用していただくことを推奨しています。Realmの操作のうちの多くは(ディスクI/Oを伴う操作などと同様に)実行時に失敗する可能性があります。そのため、クラッシュレポートを収集することは、何が原因で問題が発生したのかを特定し、エラー処理の改善や不具合の修正に有効です。
多くの商用のクラッシュレポーターはログを収集するオプションを提供しています。この機能を有効にすることを強く推奨します。Realm例外が発生した時や、致命的な状態に陥った際にはメタデータの情報をログに出力しており(そこにユーザーデータは一切含まれていません)、それらのログは我々が問題を調査する際に役に立ちます。
Realmの問題や不具合を報告するには
Realmについて問題を発見した際は、GitHubで問題を報告するか、help@realm.ioにEメール、またはSlackで報告してください。その際には、こちらで問題を再現できるように、できるだけ多くの情報をあわせて教えてください。
下記に示す情報は問題を解決するために非常に役に立ちます。
- あなたが実際にやりたいこと・目的。
- 期待している結果。
- 実際に発生した結果。
- 問題の再現方法。
- 問題を再現、または理解できるサンプルコード (そのままビルドして実行できるXcodeプロジェクトだと理想的です) 。
- Realm、Xcode、OS Xのバージョン
- (利用している場合は)CarthageやCocoaPodsなどのパッケージマネージャのバージョン
- 問題の発生するプラットフォーム(iOSやtvOSなど)、OSのバージョン、アーキテクチャ(例: 64-bit iOS 8.1)。
- クラッシュログやスタックトレース。上述のクラッシュレポートの項目も参考にしてください。
クラスにプロパティが1つも無い(No properties in class)という実行時例外が起こる
“No properties in class, has linker stripped it?”というメッセージのSystem.InvalidOperationException
が発生することがあります。
バージョン0.77.2以降では、もう少し詳しい情報が表示されます。
判明している原因は2つあります。
- Fodyに何か問題が発生していてWeavingされたRealmObjectsが1つも無い
- RealmObjectsのプロパティがStripされてしまっていてRealmが空になっている
最初のケースに関しては例外はRealmSchema
からスローされます。詳しくはWeavingに失敗したときのセクションをご覧ください。
2番目のケースに関しては例外はObjectSchema
からスローされます。 Linker Beahviour を Link 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.
このエラーが起こるのは、下記のいずれかの理由によります。
- 米国輸出規制法のリストに含まれる国のIPアドレスが割り当てられている。 米国の法律に従うために、Realmは上記のリストに含まれる国で使用することはできません。 さらに詳しいことはライセンス条項をお読みください。
- 中国国内、あるいは国レベルのファイアウォールによってCloudFlareまたはAmazon AWS S3へのアクセスを制限している国からアクセスしている。 さらに詳しいことはこちらのIssueをご覧ください。
- Amazon AWS S3が障害により一時的に利用できなくなっている。AWS Service Health Dashboardを見てサービスの稼働状況を確認し、 障害が解決された後で再度やり直してください。