Realm Blog

Realm Java 1.1.0 — Insert APIを追加!

by /

Realm Java 1.1.0をWebサイトBintrayでリリースしました。

Insert API

Realmのストレージエンジンは、データの挿入において常にSQLiteよりも高速に動作する性能を備えています。しかしRealm JavaのAPIデザイン上の制約により、ストレージエンジンの性能上のメリットはJavaのAPIからは生かせない状況が続いていました。

具体的には、1000以下のオブジェクトの一括作成においてはSQLiteと遜色のない性能が出ていましたが、これよりも多くのオブジェクトを一括作成しようとすると性能上の問題が発生します。

バージョン1.1.0ではこの問題を改善するため、新たに以下の4つのメソッドを追加しました。

  • void Realm.insert(RealmModel obj)
  • void Realm.insert(Collection collection)
  • void Realm.insertOrUpdate(RealmModel obj)
  • void Realm.insertOrUpdate(Collection collection)
Realm realm = Realm.getDefaultInstance();
realm.beginTransaction();
realm.insertOrUpdate(restApi.getPersons());
realm.commitTransaction();

これらのメソッドと従来から存在したRealm.copyToRealm()との大きな違いは、Realmに格納されたオブジェクトを返り値として返すかどうかです。返り値を省略することにより、オーバーヘッドとなっていたさまざまなチェックを省略出来るだけでなくオブジェクト作成処理における動的なメモリ割り当てを限りなくゼロに近づけることができます。

これまでは10万オブジェクトの一括作成においてSQLiteに比べて4割ほど遅かったものが、この改善によりSQLiteよりも1.7倍高速という結果を得ることができました。

Realm Batch Insert Benchmark

このベンチマーク結果を皆さんにも確認していただけるようにするため、ベンチマークコードのリポジトリを公開しました。上記のグラフの結果は、Android 6.0.1がインストールされたNexus 6P上の実行した際のものです。

Realmのauto-updateの機能を利用することで、オブジェクトの作成前にクエリを発行し作成完了時に通知を受け取ることができます。この機能はデータの表示と保存を分離する際にとても役に立ちます。

final PersonApi api = new PersonApi();
Realm realm = Realm.getDefaultInstance();
RealmResults<Person> persons = realm.where(Person.class).findAllAsync();
person.addChangeListner(new RealmChangeListener() {
    @Override
    public void onChange(RealmResults<Person> persons) {
      if (!persons.isEmpty()) {
	      // 作成完了時の処理を記述
      }
  }
});

realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
      realm.insertOrUpdate(api.getPersons());
    }
});

バグフィックス

Realm Java 1.1.0では、以下のものを含むさまざまなバグが修正されています。

  • 非同期トランザクションと非同期クエリをおなじLooperイベント内で使用した際、トランザクションで行われた操作を反映しない状態でonSuccess()コールバックが呼ばれてしまうバグを修正しました。

  • DexGuardの動作に支障があることが判明したため、1.0.1で導入されたRealmOptionalAPITransformerを一旦無効化しました。

詳細な変更点はchangelogを参照してください。


お読みいただきありがとうございます。 Realm で素晴らしいアプリケーションを作りましょう!お困りの際はStack Overflow(日本語)Slack(日本語)Twitter(日本語)GitHub(英語)でご相談ください。

Read more

13949回のcommitと6148個のissueを経てRealmがバージョン1.0になりました

by /

2014年7月に“世界初のモバイルファーストなデータベース”としてRealmを公開して以来13949回のcommitと6148個のissueを経て、本日Realm 1.0をリリースいたします。これはRealm社およびRealmプロダクトとしてだけでなく、Realmを利用していただいているiOSやAndroid開発者にとっても大きな節目であると言えます。

リリース当初はiOSとMacの開発者向けにObjective-C版を提供するのみでしたが、その後Swift版とAndroid版が加わり、つい先日React Native版とXamarin版をリリースいたしました。その結果、現在ではすべてのメジャーなモバイルプラットフォーム上の主要言語においてRealmを使用することができます。今回のRealm 1.0リリースは2年以上におよぶ開発の集大成であり、Realmをご利用いただいている皆様のご支援なしには成し得ないものでした。改めてお礼を申し上げます。

バージョン1.0に到達する以前から、Realmはモバイルアプリ開発者の方々に広く使われています。RealmはGitHubプロジェクトへの1万2千を超えるスターをいただくとともに、10万を超える開発者の方々による数万のアプリケーションにおいて実際に利用されています。これらのアプリにはStarbucks、Twitter、Anheuser-Busch、NBCUniversal、Alibaba、eBayをはじめとする大きなユーザー数をもつ企業からリリースされているアプリが含まれています。

Realmをつかうことでアプリをより良いものにすることができる、言い換えるとこれらのアプリを開発している開発者がよりよいユーザー体験をより簡単に、より短時間で開発することができるということがこれほど多くご利用いただいている理由です。また本日のバージョン1.0のリリースによって、iOS版およびAndroid版のRealmをご利用いただいている開発者の方々は成熟と安定を手にすることができます。

これまでにどのような変更が行われてきたかについては、Java版およびObjective-CとSwift版のchangelogをご参照ください。

Realmについて

RealmはSQLite上に構築されたいわゆるORMではありません。私たちはモバイルアプリケーション開発者のために1からデータベースを開発しています。そのため単なるキーバリューストアではなく、データベースエンジンが保持するデータに動的に対応付けられたネイティブなオブジェクトを提供します。このことにより、Realmは簡潔なAPIとパフォーマンスの両立を達成しています。Realmを用いることで、複雑なデータのモデリング、オブジェクト間のリンク、高度なクエリのすべてが可能となります。

// モデルの定義はRealmObjectを拡張します
public class Dog extends RealmObject {
    private String name;
    private int age;

    // ... IDEでgetter/setterを自動生成 ...
}

public class Person extends RealmObject {
    @PrimaryKey
    private long id;
    private String name;
    private RealmList<Dog> dogs; // 1対多の関連の定義です

    public Person(long id, String name) {
        this.id = id;
        this.name = name;
    }

    // ... IDEでgetter/setterを自動生成 ...
}

// 使い方は通常のJavaオブジェクトと同様です
Dog dog = new Dog();
dog.setName("Rex");
dog.setAge(1);

// デフォルトではアプリの"files"ディレクトリにファイルを保存します
RealmConfiguration realmConfig = new RealmConfiguration.Builder(context).build();
Realm.setDefaultConfiguration(realmConfig);

// 現在のスレッドで有効なRealmインスタンスを取得します
Realm realm = Realm.getDefaultInstance();

// ageの値が2より小さいDogクラスのオブジェクトを検索して取得します
final RealmResults<Dog> puppies = realm.where(Dog.class).lessThan("age", 2).findAll();
puppies.size(); // => まだ何もオブジェクトを保存していないので0が返ります

// トランザクションを用いてデータを保存します
realm.beginTransaction();
final Dog managedDog = realm.copyToRealm(dog); // オブジェクトを引数に渡すと保存されます
Person person = realm.createObject(Person.class); // オブジェクトの生成と保存を同時に行う例です
person.getDogs().add(managedDog);
realm.commitTransaction();

// データが更新されるとリスナーに通知が送られます
puppies.addChangeListener(new RealmChangeListener<RealmResults<Dog>>() {
    @Override
    public void onChange(RealmResults<Dog> results) {
        // クエリの結果は自動的に更新されます
        puppies.size(); // => 1
    }
});

// バックグラウンドで非同期にオブジェクトを更新する例です
realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm bgRealm) {
        Dog dog = bgRealm.where(Dog.class).equals("age", 1).findFirst();
        dog.setAge(3);
    }
}, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
      // もともとの検索結果も自動的に更新されます
      puppies.size(); // => ageの値が2より小さいオブジェクトは無くなったので、0が返ります
      managedDog.getAge();   // => このオブジェクトの値が更新されたので3が返ります
    }
});
// モデルの定義は通常のObjective‑Cのクラスを定義するときと同じです
@interface Dog : RLMObject
@property NSString *name;
@property NSData   *picture;
@property NSInteger age;
@end
@implementation Dog
@end
RLM_ARRAY_TYPE(Dog)
@interface Person : RLMObject
@property NSString             *name;
@property RLMArray<Dog *><Dog> *dogs;
@end
@implementation Person
@end

// 使い方は通常のObjective‑Cのオブジェクトと同様です
Dog *mydog = [[Dog alloc] init];
mydog.name = @"Rex";
mydog.age = 1;
mydog.picture = nil; // プロパティをNULL可/不可にすることができます
NSLog(@"Name of dog: %@", mydog.name);

// ageの値が2より小さいDogクラスのオブジェクトを検索して取得します
RLMResults<Dog *> *puppies = [Dog objectsWhere:@"age < 2"];
puppies.count; // => まだ何もオブジェクトを保存していないので0が返ります

// オブジェクトの保存は下記のように非常に簡単です
RLMRealm *realm = [RLMRealm defaultRealm];
[realm transactionWithBlock:^{
  [realm addObject:mydog];
}];

// 検索結果は自動的に更新されます
puppies.count; // => 1

// バックグラウンドスレッドでオブジェクトを検索し、更新する例です
dispatch_async(dispatch_queue_create("background", 0), ^{
  Dog *theDog = [[Dog objectsWhere:@"age == 1"] firstObject];
  RLMRealm *realm = [RLMRealm defaultRealm];
  [realm beginWriteTransaction];
  theDog.age = 3;
  [realm commitWriteTransaction];
});
// モデルの定義は通常のSwiftクラスを定義するときと同じです
class Dog: Object {
  dynamic var name = ""
  dynamic var age = 0
}
class Person: Object {
  dynamic var name = ""
  dynamic var picture: NSData? = nil // Optional型もサポートしています
  let dogs = List<Dog>()
}

// 使い方は通常のSwiftオブジェクトと同様です
let myDog = Dog()
myDog.name = "Rex"
myDog.age = 1
print("name of dog: \(myDog.name)")

// デフォルトの設定でRealmインスタンスを取得します
let realm = try! Realm()

// ageの値が2より小さいDogクラスのオブジェクトを検索して取得します
let puppies = realm.objects(Dog).filter("age < 2")
puppies.count // => まだ何もオブジェクトを保存していないので0が返ります

// オブジェクトの保存は下記のように非常に簡単です
try! realm.write {
  realm.add(myDog)
}

// 検索結果は自動的に更新されます
puppies.count // => 1

// バックグラウンドスレッドでオブジェクトを検索し、更新する例です
dispatch_async(dispatch_queue_create("background", nil)) {
  let realm = try! Realm()
  let theDog = realm.objects(Dog).filter("age == 1").first
  try! realm.write {
    theDog!.age = 3
  }
}
// モデルとプロパティの属性を定義します
class Car {}
Car.schema = {
  name: 'Car',
  properties: {
    make:  'string',
    model: 'string',
    miles: 'int',
  }
};
class Person {}
Person.schema = {
  name: 'Person',
  properties: {
    name:    {type: 'string'},
    cars:    {type: 'list', objectType: 'Car'},
    picture: {type: 'data', optional: true}, // プロパティをOptional(NULL可)にする例です
  }
};

// モデルの定義を渡してRealmインスタンスを取得します
let realm = new Realm({schema: [Car, Person]});

// オブジェクトを作成して保存します
realm.write(() => {
  let myCar = realm.create('Car', {
    make: 'Honda',
    model: 'Civic',
    miles: 1000,
  });
  myCar.miles += 20; // プロパティの値を更新する例です
});

// milesの値が1000より大きいCarオブジェクトを検索して取得します
let cars = realm.objects('Car').filtered('miles > 1000');

// 1件のCarオブジェクトが返ります
cars.length // => 1

// もう一つCarオブジェクトを追加します
realm.write(() => {
  let myCar = realm.create('Car', {
    make: 'Ford',
    model: 'Focus',
    miles: 2000,
  });
  
// 検索結果は自動的に更新されます
cars.length // => 2
// モデルの定義は通常の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が返ります

// トランザクションを用いてオブジェクトを保存・更新します
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を使うべき理由

使い方が簡単

Realmは使いやすさを最も重視しています。規模の大きなアプリでも数時間でRealmに対応させることができ、実装、最適化、デバッグのための時間を数週間節約できたということも珍しくありません。

速い!

Realmの使いやすさはパフォーマンスを犠牲にしません。メモリマッピング、遅延ローディング、独自のストレージエンジンなどにより、RealmはリッチなオブジェクトベースのAPIを提供しているにもかかわらず、通常SQLiteよりも高速に動作します。それぞれのユースケースでの確認を行うことをおすすめしていますが、多くの場合既存のコードをRealmに対応させることで大幅な高速化を達成できます。

クロスプラットフォーム

RealmはJavaObjective-CReact NativeSwiftXamarinをサポートしています。これらのプラットフォーム間でRealmファイルは互換性があります。また、これらすべてのプラットフォームで同じようなロジックを用いてアプリを実装することができます。デバッグの際は、.realmファイルの中身をRealm Browserを用いて確認することができます。

先進的

Realmオブジェクトはつねに最新の状態に維持され、そのことがリアクティブパターンや一方向データフローを当たり前のものにします。リンクの機能はグラフ構造のRealmオブジェクトの扱いを可能にし、先進的なクエリ言語を用いることができます。また、AES256による暗号化機構を持ち、アドオンクラスを用いることでRealm上のデータを簡単にUIに表示することができます。

信頼性

大規模なアプリや多数のユーザーがいる場合でも安心です。Realmはすでに銀行のアプリケーション、ヘルスケア分野、複雑なエンタープライズアプリ、AppleのAppストアやGoogle Playストアでナンバーワンになるようなアプリでも採用されています。

コミュニティドリブン

RealmはGitHub上でオープンに開発されています。機能はユーザーの要求によって優先順位付けされるだけでなく、コードのコントリビューションも歓迎しています。Realmのプロジェクトは1万2千を超えるスターをGitHub上で獲得し、たくさんのアプリ、プラグイン、コンポーネントが作られています。

サポート

Realmはサポートとバグフィックスを他のすべてのタスクよりも優先します。そのため、データベースに関する質問には直接その開発やメンテナンスを行っている開>発者から回答を得ることができます。 質問はSlack(日本語)Twitter(日本語)Stack Overflow(日本語)GitHub(英語)で受け付けています。

Realmの利用者について

Realmはさまざまなアプリに組み込まれ、10億人を超えるユーザーに利用されています。

Twitter

Appストアで大成功を収めたアプリがRealmを使用しています。Twitter社の動画アプリで2億人のMAUを抱えるVineが2016年からRealmを使い始めました。

Starbucks

フォーチュン500の小売り企業も主力アプリにRealmを使用しています。来店前にスマートフォンから注文し、カウンターですぐに受け取ることができます。

Alibaba

NYSEに上場している中国の巨大企業もRealmを使用しています。マーケット上での売り買いのためのアプリで使用され、昨年はおよそ30億ドルの売上を達成しています。

Budweiser

フォーチュン・グローバル500の巨大企業はTapWiserという革新的なアプリでRealmを使っています。バーや小売店がパソコン無しに素早くビールの樽や瓶を注文できるモバイルアプリです。

SAP

もしすでにConcurというアプリを使ったことがあるなら、あなたはすでにRealmユーザーかも知れません。経費精算やトラベルプランのためのアプリで、2万社に3000万人のエンドユーザーがいます。

Rite Aid

フォーチュン500の巨大小売業者もEnvisionRxというアプリで安全にお客様のデータを扱うためRealmを使っています。

ここにあげた以外にもAmazon、eBay、Google、GoPro、Walmart、Adidas、Cisco、NBCUniversal、Nike、NFLなど数多くの企業で、また日本ではAWA株式会社、日本経済新聞社、Sansan株式会社、株式会社マネーフォワード、ヤフー株式会社、LINE株式会社、株式会社サイバーエージェントなど、皆さんご存知の企業のアプリでRealmをご利用いただいています。

What’s Next

ぜひ改善に関するフィードバックをお寄せください。お困りの際はStack Overflow(日本語)Slack(日本語)Twitter(日本語)でご相談ください。

Realm Javaの公式ドキュメントおよびGitHubリポジトリ
Realm Objective-Cの公式ドキュメントおよびGitHubリポジトリ
Realm Swiftの公式ドキュメントおよびGitHubリポジトリ
Realm React Nativeの公式ドキュメントおよびGitHubリポジトリ
Realm Xamarinの公式ドキュメントおよびGitHubリポジトリ

Realmがどのようなプロダクトに使われることになるのか、私たちはとても楽しみにしています。

Read more

Realm Xamarinを公開!

by /

本日、Xamarin向けの新たなRealmモバイルデータベースを公開いたしました。オブジェクトの永続化とフル機能のクエリをサポートしたデータベースでありながら既存のものよりも高速に動作します。

他の言語向けのRealmと同様、reactiveなアプリの開発のために設計されたもので、ライブオブジェクト変更通知イベント一方向のデータフローをサポートしています。

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

var realm = Realm.GetInstance();

// LINQでクエリを発行
var puppies = realm.All<Dog>().Where(d => d.Age < 2);
puppies.Count(); // => 0(オブジェクト未作成なので)

// トランザクションを用いて書き込み
realm.Write(() =>
{
    var mydog = realm.CreateObject<Dog>();
    mydog.Name = "Rex";
    mydog.Age = 1;
});

// クエリの結果はリアルタイムに更新されます
puppies.Count(); // => 1

Realm はモバイルアプリケーションのために作り上げられたデータベースであり、携帯電話、タブレット、ウエアラブル端末上で動作します。2014年にJava版、Objective‑C版、Swift版をリリースし、2016年初頭にはReact Nativeをリリースしています。

これまでLINE、Yahoo、サイバーエージェント、Wantedly、日経新聞、Sansan、マネーフォワード、Starbucks、Cisco、Walmart、Google、Amazon、eBayをはじめとするさまざまな企業のアプリケーションに利用され、数億台のデバイスで稼働しています。

本日、Microsoft社が提供するモバイル向けのフレームワークであり、C#のコードでネイティブのiOS/Androidアプリ開発を可能にするXamarin向けのRealmを公開いたします。

Realm Xamarin は先進的な設計と簡潔さをアプリケーションにもたらし、単一のコードベースでiOSとAndroidの両方に対応することを可能にします。当初サポートするのはXamarin.iOSおよびXamarin.Androidですが、将来的にはXamarin.Mac、UWP、Unityのサポートも行っていきたいと考えています。

What is Realm?

RealmはSQLite上に構築されたいわゆるORMではありません。我々はモバイルアプリケーション開発者のために1からデータベースを開発しています。そのため単なるキーバリューストアではなく、データベースエンジンが保持するデータに動的に対応付けられたネイティブなC#オブジェクトを提供します。このことにより、Realmは簡潔なAPIとパフォーマンスの両立を達成しています。Realmを用いることで、複雑なデータのモデリング、オブジェクト間のリンク、高度なクエリのすべてが可能となります。

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

var realm = Realm.GetInstance();

realm.Write(() =>
{
    var mydog = realm.CreateObject<Dog>();
    mydog.Name = "Rex";
    mydog.Age = 9;
});
// LINQによるクエリ
var oldDogs = realm.All<Dog>().Where(d => d.Age > 8);
// or
var oldDogs = from d in realm.All<Dog>() where d.Age > 8 select d;

// クエリは連鎖可能
var dogsNamedRex = oldDogs.Where(p => p.Name == "Rex");
dogsNamedRex.Count(); // => 1

// 変更はスレッドセーフなトランザクション内で実行します
realm.Write(() =>
{
    var mydog = realm.CreateObject<Dog>();
    mydog.Name = "Rex Maximus";
    mydog.Age = 10;
});

// クエリの結果はリアルタイムに更新されます
dogsNamedRex.Count(); // => 2
public class Person : RealmObject
{
    public string Name { get; set; }
    public RealmList<Dog> Dogs { get; }
}

var realm = Realm.GetInstance();

var jim = realm.CreateObject<Person>();

realm.Write(() =>
{
    var mydog = realm.CreateObject<Dog>();
    mydog.Name = "Fido";
    mydog.Owner = jim;
});

APIの使用方法に関するさらなる例はサンプルアプリを参照してください。

Realmを使うべき理由

使い方が簡単

上記のサンプルコードを見ていただければ分かる通り、Realmは使いやすさを最も重視しています。これはRealm Xamarinでも変わりありません。使いやすさにつづき、以下の点についても他のプロダクト同様注力しています。

速い!

Realmの使いやすさはパフォーマンスを犠牲にしません。メモリマッピング、遅延ローディング、独自のストレージエンジンなどにより、RealmはリッチなオブジェクトベースのAPIを提供しているにもかかわらず、通常SQLiteよりも高速に動作します。それぞれのユースケースでの確認を行うことをおすすめしていますが、多くの場合既存のコードをRealmに対応させることで大幅な高速化を達成できます。詳細はベンチマーク結果を参照してください。

クロスプラットフォーム

Realm XamarinのAPIはC#で記述され、iOSとAndroidの両方を対象とすることができます。さらに、Realmのデータベースファイルフォーマットは完全にクロスプラットフォーム対応なので、iOSとAndroid間で簡単に同じファイルを使用することができます。デバッグの際は、.realmファイルの中身をRealm Browserを用いて確認することができます。

先進的

Realmオブジェクトはつねに最新の状態に維持され、そのことがリアクティブパターンや一方向データフローを当たり前のものにします。リンクの機能はグラフ構造のRealmオブジェクトの扱いを可能にし、LINQにより任意のプロパティの組み合わせに対してクエリを発行することができます。また、Realmのデータを簡単にXamarin.Formsに統合することができます。

信頼性

Realm Xamarinは世界中の数億のユーザーに利用されているRealm Java、Objective‑C、Swiftと同じエンジンの上に構築されています。これらはeコマースアプリケーション、銀行のアプリケーション、ヘルスケア分野、さらには政府機関のアプリケーションにも採用されています。

コミュニティドリブン

Realm Xamarinは、GitHub上で開発が行われています。機能はユーザーの要求に応じて優先順位付けされます。また、コードへのコントリビューションも歓迎しています。

サポート

Realmはサポートとバグフィックスを他のすべてのタスクよりも優先します。そのため、データベースに関する質問には直接その開発やメンテナンスを行っている開発者から回答を得ることができます。 質問はSlack(日本語)Twitter(日本語)StackOverflow(日本語)GitHub(英語)で受け付けています。

テストは2016年5月9日時点の最新版のRealm、sqlite-net、Couchbase Liteを使用しています。また、測定はiPhone 6S Plus 128GB上にiOS 9.3.1がインストールされた端末で行っています(ソースコード)。ベンチマークはおおまかな目安なので、ユースケースに合わせた測定を各自で行うことをおすすめします。

今後について

ぜひ改善に関するフィードバックをお寄せください。バグレポートや機能のリクエストはGitHubリポジトリで受け付けています。今後数週間の間APIの改善を進めてまいりますが、特にマイグレーションやクエリに関するAPIの改善に力を入れていきます。

.NETを使用していてUWPサポートUnity サポートが必要な方は、ぜひこれらのissueにご意見をお寄せください。

Realmがどのようなプロダクトに使われることになるのか、我々はとても楽しみにしています。

Read more

Realm Objective‑C & Swift 0.102 – マイグレーションにおけるプロパティ名の変更と、ファイルの自動削除をサポートしました。

by /

Xcode 6.xおよび2.2より古いSwiftはこのバージョンで非推奨となります。次のリリースでは削除されます。

Realm Objective‑CおよびRealm Swift 0.102をリリースしました。

マイグレーションに関して2つの改善を加えました。1つはマイグレーションにおいてプロパティ名を変更できるようになりました。もう一つはマイグレーションが必要なときに自動的に既存のファイルを削除することができる設定を追加しました。

マイグレーション時のプロパティ名の変更

マイグレーションにおいてプロパティ名を変更できるようになりました。プロパティ名の変更は以前のように値をコピーする必要がなく、関連も保持されるので効率的に動作します。

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

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

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

// application(application:didFinishLaunchingWithOptions:)の中に書きます

Realm.Configuration.defaultConfiguration = Realm.Configuration(
  schemaVersion: 1,
  migrationBlock: { migration, oldSchemaVersion in
    // 初めてマイグレーションを実行するのでoldSchemaVersionは0です
    if (oldSchemaVersion < 1) {
      // 名前の変更は`enumerate(_:)`の外側で実行する必要があります。
      migration.renamePropertyForClass(Person.className(), oldName: "yearsSinceBirth", newName: "age")
    }
  })
// [AppDelegate didFinishLaunchingWithOptions:]の中に書きます

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

マイグレーションが必要な場合にRealmファイルを削除する

開発の最中にスキーマが変更されることはよく起こります。しかし、そのたびに完全なマイグレーションを書かなければいけないとしたら、非常に大変です。

そこで、このバージョンでは、deleteRealmIfMigrationNeededというBool型のプロパティをRLMRealmConfiguration/Realm.Configurationに追加しました。このフラグはデフォルトではfalseに設定されているので、自分で有効にしない限りは、Realmのバージョンをアップデートしても予期せずデータが失われることはありません。

不具合の修正

  • 0.101.0で導入された新しいファイルフォーマットにアップグレードする際にクラッシュする問題を修正しました。
  • RLMArray/Listプロパティに対してBETWEEN句のクエリを実行した際に、個々の要素が一致するかどうかを正しく判定するように修正しました。
  • 逆方向の関連が指すオブジェクトが削除されていた場合にクラッシュする問題を修正しました。
  • 他のスレッドがRealmファイルを閉じている最中に、別のスレッドがRealmファイルを開こうとした際に競合が発生する問題を修正しました。

お読みいただきありがとうございます。 Realm で素晴らしいアプリケーションを作りましょう!お困りの際はStack Overflow(日本語)Slack(日本語)Twitter(日本語)GitHub(英語)でご相談ください。

Read more

Realm Objective‑C & Swift 0.101 – 日付型をナノ秒までサポート、メモリ使用量の減少、マルチプロセスにおけるクラッシュの取り扱いを改善

by /

このバージョン(0.101)で読み書きを行ったRealmファイルは自動的にフォーマットがアップグレードされ、以前のバージョンで開くことができなくなります。既存アプリケーションに対して更新を適用する場合は特に注意してください。

Realm Objective‑CおよびRealm Swift 0.101をリリースしました。

このバージョンでは、これまでずっと存在していた、日付型の精度の制限を取り除きました。さらに、メモリマップの際のメモリ使用量の削減とマルチプロセスにおけるクラッシュの取り扱いを改善しました。

ナノ秒までの精度の日付 & 新しいファイルフォーマット

これまでは、NSDate型のプロパティはミリ秒以下は切り捨てられていました。そのため、ミリ秒の精度を求めるアプリケーションにおいては、正しく動作しませんでした。このバージョンではNSDateよりもさらに高い精度の新しい日付のフォーマットが含まれています。日付型のプロパティは完全な精度を保持できるようになりました。

新しい日付型のフォーマットを利用するためには、Realmファイルを完全に新しいフォーマットに更新する必要があります。つまり、このバージョンで読み書きしたRealmファイルは以前のバージョンでは開くことができなくなります。既存のファイルは、読み取り専用のAppバンドル内のファイルを開いたとき以外は、このバージョンで開いたときに自動的にアップグレードされます。Realm Browserでファイルを開いたときも同様にアップグレードされます。リリース済みのアプリケーションに対して更新をする場合は、特に注意してください🚨

マルチスレッドにおけるメモリマップのオーバヘッドを削減

以前のバージョンのRealmではトランザクションを隔離するために、スレッドごとにファイル全体をmmapする必要がありました。これは他のデータ永続化フレームワークがよく備えている「フォールト」という概念をRealmが持たない理由です。

このバージョンでは、複数のスレッドから一つのRealmファイルを開いている場合は、一つのメモリマップをすべてのスレッドで共有するようになりました。このため、特に大きなサイズのファイルを取り扱う際に、非常にメモリの必要量を削減することができます。

複数プロセス間のクラッシュの取り扱いを改善

同じファイルに対して複数のプロセスがトランザクションを実行している際に、あるプロセスがクラッシュした場合に、他のプロセスをブロックしないようになりました。

以前に掲載した、Fast Inter-Process CommunicationRealm 0.91リリースの記事に書かれた、Darwinはプロセス間の堅牢なMutexをサポートしていないという事実を覚えていますでしょうか。そのため、私たちはRealmの最も基本的なロックの仕組みを、fcntl(3)を使い再実装し、POSIXのAPIを完全にサポートしないプラットフォームでも動作する、堅牢なプロセス間のMutexをエミュレートしました。

このことにより、ロックを持ったプロセスがクラッシュした場合でも、他のプロセスをロックすることがなくなりました。

その他の改善

複雑な関連を持ったオブジェクトに対する通知のパフォーマンスを大幅に改善しました。同時に、Results/RLMResultsに対する更新(自動更新を含む)のパフォーマンスも向上しました。

不具合の修正

RLMArrayまたはListが異なるスレッドで解放された際のクラッシュを修正しました。


お読みいただきありがとうございます。 Realm で素晴らしいアプリケーションを作りましょう!お困りの際はStack Overflow(日本語)Slack(日本語)Twitter(日本語)GitHub(英語)でご相談ください。

Read more

Realm Java 0.90 — Better Date and API Cleanup!

by /

Realm Javaの最新版をwebサイトJCenterでリリースしました。

このリリースで、Date型のミリ秒精度のサポートとAPIクリーンアップが行われました。

Date型の精度改善

Realm Javaがjava.util.Date型をサポートし始めた当初から1つの制限事項がありました。それは時刻の情報を秒の精度に丸めてしまうというものです。

この制限が導入されたのはすべてのRealmプロダクト間で互換性を保つためでしたが、Realm Java 0.90及びRealm Objective-C & Swift 0.101からはこの制限を撤廃し、Date型が提供するミリ秒精度の日時情報をRealmでも扱えるようにしました。

この変更はRealmファイルのフォーマット変更を伴いますが、Realm Java 0.90以降でファイルを開いた場合は自動的にフォーマット変換が行われるため通常はアプリケーション開発者が気にする必要はありません。

ミリ秒精度のDate型をぜひご活用ください。

Android Adapters

0.90から、Android特有のコンポーネントを別のリポジトリに移します。このことにより、機能拡張やバグ修正のリリースがRealm本体とは独立して行えるようになるだけでなく、ライブラリ本体のサイズも小さくすることができます。

この変更によりRealmBaseAdapterはRealm本体には含まれなくなるため、継続して使用するためには以下の依存を追加する必要があります。

dependencies {
	compile 'io.realm:android-adapters:1.0.1'
}

将来的にはRecyclerViewCursorのようなAndroid固有の機能をサポートするクラスを提供する予定です。これら以外にもサポートしてほしいコンポーネントの要望がある場合は、Realm Android Adaptersリポジトリーにissueを作成してください。

APIクリーンアップ

Realm Javaがベータになってからおよそ1.5年が経過しましたが、その間にAPIに関するさまざまなフィードバックをいただきました。これらのフィードバックをふまえ、APIの削除や変更を行いました。

APIの変更のうち重要なものを以下に列挙します。

  • すべてのAPIのクラスをfinalにしました。

  • RealmChangeListenerは、変更対象のオブジェクトをonChange()の引き数で受け取れるようになりました。

realm.addChangeListener(new RealmChangeListener<Realm>() {
	@Override
	public void onChange(Realm realm) {
		// Realmの参照を受け取ることができます。
		// Realmのオブジェクトは自動的に更新されるため、このメソッド内で取得した
		// オブジェクトの持つ値と、別の場所で保持しておいた同一のRealmオブジェクト
		// の値を比較することは意味がありません(すべて最新の値に更新されます)。
		// すべて最新の状態に更新されるので
    }
});

Person p = realm.where(Person.class).findFirst();
p.addChangeListener(new RealmChangeListener<Person>() {
	@Override
	public void onChange(Person person) {
		person.getName(); // 更新のあったPerson
	}
});
  • RealmおよびDynamicRealm上のクエリ系のメソッド(allAbject*()およびdistinct*())を非推奨にしました。これらの機能を使用する場合はRealm.where()DynamicRealm.where()RealmCollection.where()から取得できるRealmQueryオブジェクトのメソッドを使用してください。

  • RealmConfiguration.Builder.setModules()RealmConfiguration.Builder.modules()にリネームされました。

  • Realm.waitForChange()が新たに導入されRealm.refresh()は非推奨になりました。Realm.waitForChange()の使い方についてはこのgistを参照してください。

この変更はワンタイムのタスクは非同期APIを使用するか、必要に応じてRealmインスタンスをオープンし完了したら即座にcloseするかのいずれかであるべきという理由で採用されました。 waitForChange()は、Looperスレッドではない場合のように、非同期APIが利用できないユースケースで利用に適しています。

waitForChange()メソッドは、現状のデータに対する変更が発生するかstopWaitForChange()が呼び出されるまで呼び出しスレッドをブロックします。

完全な変更内容一覧と詳細はchangelogから参照することができます。

お読みいただきありがとうございます。Realmが素晴らしいアプリの実装のお役に立てれば嬉しく思います。お困りの際はStack Overflow(日 本語)Slack(日本語)Twitter(日本語)GitHub(英語)でご相談ください。

Read more

Realm Objective‑C & Swift 0.100 – クエリに指定可能で自動更新が有効な逆方向の関連をサポートしました!

by /

Realm Objective‑CおよびRealm Swift 0.100をリリースしました。

このバージョンでは、非常に多くのリクエストをいただいていた、もうひとつの機能である、逆方向の関連について、クエリの条件に指定可能にすることと、自動更新がサポートされました。このことにより、逆方向の関連は通常の関連とまったく同様に取り扱えるようになります。

逆方向の関連がクエリの条件に指定可能、自動更新が有効

まず簡単にRealmにおいて関連がどのように機能するのかをご説明します。関連は基本的に一方向でRLMArray/Listまたはオブジェクト型のプロパティとして表現されます。これらの関連のプロパティはクエリの条件に指定することができ、アプリの別の場所で更新があっても自動的に最新の内容が反映されます。

逆方向の関連を取得するためには、Realmは以前からその時点のスナップショットを返すためのメソッドを用意していました。Realm 0.100ではその動作を改善し、逆方向の関連をLinking Objectと呼ばれるプロパティで表現することが可能になりました。このプロパティは自動更新可能で、クエリの条件に指定することができ、他のコレクション型のオブジェクトと同様にコレクションに対するクエリも使用することができます。

唯一他のプロパティと異なる点は、Linking Objectは直接変更することができないという点です。Linking Objectは正方向の関連であるRLMArray/Listまたはオブジェクト型のプロパティに変更が加えられることによって、その結果として変更されます。

1対多の関連

@interface Dog : RLMObject
@property NSString *name;
@property NSInteger age;
@property Person *owner;
@end

@implementation Dog
@end

@interface Person : RLMObject
@property NSString *name;
@property NSInteger age;
@property (readonly) RLMLinkingObjects<Dog *> *dogs;
@end

@implementation Person
+ (NSDictionary *)linkingObjectsProperties {
    return @{
        @"dogs": [RLMPropertyDescriptor descriptorWithClass:Dog.class propertyName:@"owner"],
    };
}
@end
class Dog: Object {
    dynamic var name = ""
    dynamic var age = 0
    dynamic var owner: Person?
}

class Person: Object {
    dynamic var name = ""
    dynamic var age = 0
    let dogs = LinkingObjects(fromType: Dog.self, property: "owner")
}

多対多の関連

@interface Person : RLMObject
@property NSString *name;
@property NSInteger age;
@property RLMArray<Person *><Person> *children;
@property (readonly) RLMLinkingObjects<Person *> *parents;
@end

@implementation Person
+ (NSDictionary *)linkingObjectsProperties {
    return @{
        @"parents": [RLMPropertyDescriptor descriptorWithClass:Person.class propertyName:@"children"],
    };
}
@end
class Person: Object {
    dynamic var name = ""
    dynamic var age = 0
    let children = List<Person>()
    let parents = LinkingObjects(fromType: Person.self, property: "children")
}

直接変更できないという性質のため、RLMLinkingObjects/LinkingObjects型のプロパティはreadonly/letとして宣言する必要があります。

新しいLinking Object型のプロパティを使うことは、これまでの方法に比べていくつか大きな利点があります。

1) Linking Objectは自動更新可能なコレクション

他のコレクション型と同様に、RLMLinkingObjects/LinkingObjectsは自動更新が働きます。新しく関連が追加されたり、削除されたりすると、Linking Objectは自動的に最新の状態を反映するように変更されます。つまり、もう必要に応じて静的なスナップショットを自分で取得する必要はありません。

2) Linking Objectはクエリの条件に使用可能

古いAPIでは逆方向の関連を使ったクエリは実行できなかったので、自分でオブジェクトをフィルタするようなコードを書く必要がありました。そのため、Realmのクエリを使用するよりはパフォーマンスが悪く、自動更新も利用できませんでした。

新しいLinking Objectは完全にRealmのクエリシステムに統合されているので、逆方向の関連に対して効率よく、簡単にフィルタを実行することができます。

// 親の名前がDianeである子を持つ人を検索する。
[PersonObject objectsWhere:@"ANY children.parents.name == 'Diane'"];

// 両親の平均年齢が65歳より高い人を検索する。
[PersonObject objectsWhere:@"[email protected] > 65"];
// 親の名前がDianeである子を持つ人を検索する。
realm.objects(Person).filter("ANY children.parents.name == 'Diane'")

// 両親の平均年齢が65歳より高い人を検索する。
realm.objects(Person).filter("[email protected] > 65")

3) Linking ObjectはRealmコレクションと同様です

かつての古いAPIは標準ライブラリの配列を返していました。新しいRLMLinkingObjects/LinkingObjects型はRealmコレクションプロトコルに準拠しています。そのため、使い慣れたRLMResults/Resultsなどと同様の機能をすべて利用することができます。

// 両親のうち56歳より年上なのはどちら?
[self.parents objectsWhere:@"age > 56"];

// 両親の平均年齢を計算する
[self.parents averageOfProperty:"age"];
// 両親のうち56歳より年上なのはどちら?
self.parents.filter("age > 56")

// 両親の平均年齢を計算する
self.parents.average("age")

非推奨となるAPI

-[RLMObject linkingObjectsOfClass:forProperty:]Object.linkingObjects(_:forProperty:)のメソッドは非推奨となります。将来のリリースでは削除される可能性があります。

その他の変更

  • 同値性を調べるクエリにおいて、複数レベルのキーパスがサポートされます。
  • RLMArray / List型のプロパティを!=を用いて比較するクエリについて、正しい結果を返すようになりました。
  • オブジェクトの通知が有効なとき、書き込みトランザクションの後でRLMArray/Listを削除するようなトランザクションが実行された場合、アサーションに失敗していた問題を修正しました。

お読みいただきありがとうございます。 Realm で素晴らしいアプリケーションを作りましょう!お困りの際はStack Overflow(日本語)Slack(日本語)Twitter(日本語)GitHub(英語)でご相談ください。

Read more

Realm Objective‑C & Swift 0.99 – Fine-Grained Notifications!

by /

Realm Objective‑CおよびRealm Swift 0.99をリリースしました。

このバージョンには、2年前のRealmのリリース当時から非常に多くのリクエストをいただいていた、Fine-grained change notifications(きめ細やかな変更通知)が含まれています。

また、Objective-CとSwift双方のAPIをすべて見直し、重複や不要になったAPIを洗い出しました。そのため少しの非互換の変更が含まれます。APIをよりわかりやすく一貫性のあるものにするための措置ですのでご了承ください。

Fine-Grained Notifications(きめ細やかな変更通知)

このバージョンではコレクションに対する変更通知(Realm 0.98で導入されました)を改善し、新しくchangesパラメータが通知ブロックに渡されるようになりました。

このRLMCollectionChange/RealmCollectionChange型のパラメータは、何が変更されたのかを示します。1つ前に通知を受けたときから追加、削除、および変更されたオブジェクトのインデックスが格納されています。これまでは通知のタイミングでただすべてを再読み込みするしかありませんでしたが、この改善により、よりきめ細やかなアニメーションや表示を伴ったUIの更新を行うことができます。

インデックスの配列はUITableViewの更新APIの規約に従っており、インデックスパスに変換するだけでテーブルビューを更新するメソッドにそのまま渡すことができます。下記はテーブルビューを更新する例です。

Fine-Grained Notifications

例えば、セクションが1つのテーブルビューの場合は、次のように書けばいいです。

notificationToken = results.addNotificationBlock { changes
  switch changes {
  case .Initial:
    // この時点でクエリは実行済みのため、ResultsへのアクセスはUIをブロックしません
    self.tableView.reloadData()
    break
  case .Update(_, let deletions, let insertions, let modifications):
    // Resultsに変更があったので、UITableViewに変更を適用します
    self.tableView.beginUpdates()
    self.tableView.insertRowsAtIndexPaths(insertions.map { NSIndexPath(forRow: $0, inSection: 0) },
        withRowAnimation: .Automatic)
    self.tableView.deleteRowsAtIndexPaths(deletions.map { NSIndexPath(forRow: $0, inSection: 0) },
        withRowAnimation: .Automatic)
    self.tableView.reloadRowsAtIndexPaths(modifications.map { NSIndexPath(forRow: $0, inSection: 0) },
        withRowAnimation: .Automatic)
    self.tableView.endUpdates()
    break
  case .Error(let error):
    // バックグラウンドのワーカースレッドがRealmファイルを開く際にエラーが起きました
    fatalError("\(error)")
    break
  }
}
__weak typeof(self) weakSelf = self;
self.notificationToken = [[Person objectsWhere:@"age > 5"] addNotificationBlock:^(RLMResults<Person *> *results, RLMCollectionChange *change, NSError *error) {
  if (error) {
    NSLog(@"Failed to open Realm on background worker: %@", error);
    return;
  }

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

  // Resultsに変更があったので、UITableViewに変更を適用します
  [tableView beginUpdates];
  [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0]
                   withRowAnimation:UITableViewRowAnimationAutomatic];
  [tableView endUpdates];
}];

詳しくはドキュメントのコレクションの変更に対する通知をご覧ください。

APIの変更

時間をかけてRealmに機能を追加してきたので、整合性のためにAPI全体を見直しました。

変更点は下記のカテゴリに分類されます。

  1. RLMNotificationToken/NotificationTokenの通知を解除するメソッドは-stopだけになりました。
  2. encryptionKeyパラメータを取るメソッドと取らないメソッドがありましたが、取るメソッドに統合されました。
  3. RLMRealm/RealmRLMRealmConfiguration/Realm.Configurationの両方に存在するプロパティはConfigurationクラスのプロパティに統合されました。Realm側のプロパティは非推奨になります。
  4. これまで文字列で渡していたファイルパスの引数とプロパティはNSURL型に変更されました。これは今後のアップルの標準APIに沿うためのものです。
  5. Swift 2への移行時に変更し忘れていて、NSErrorPointerを使用するAPIを、Swift標準のエラーハンドリング(throws)を使うように修正しました。

下記の表はObjective-CのAPIにおける、非推奨のメソッドと代替メソッドの対応表です。

Deprecated API New API
-[RLMRealm removeNotification:] -[RLMNotificationToken stop]
RLMRealmConfiguration.path RLMRealmConfiguration.fileURL
RLMRealm.path RLMRealmConfiguration.fileURL
RLMRealm.readOnly RLMRealmConfiguration.readOnly
+[RLMRealm realmWithPath:] +[RLMRealm realmWithURL:]
+[RLMRealm writeCopyToPath:error:] +[RLMRealm writeCopyToURL:encryptionKey:error:]
+[RLMRealm writeCopyToPath:encryptionKey:error:] +[RLMRealm writeCopyToURL:encryptionKey:error:]
+[RLMRealm schemaVersionAtPath:error:] +[RLMRealm schemaVersionAtURL:encryptionKey:error:]
+[RLMRealm schemaVersionAtPath:encryptionKey:error:] +[RLMRealm schemaVersionAtURL:encryptionKey:error:]

下記の表はSwiftのAPIにおける、非推奨のメソッドと代替メソッドの対応表です。

Deprecated API New API
Realm.removeNotification(_:) NotificationToken.stop()
Realm.Configuration.path Realm.Configuration.fileURL
Realm.path Realm.Configuration.fileURL
Realm.readOnly Realm.Configuration.readOnly
Realm.writeCopyToPath(_:encryptionKey:) Realm.writeCopyToURL(_:encryptionKey:)
schemaVersionAtPath(_:encryptionKey:error:) schemaVersionAtURL(_:encryptionKey:)

その他の非互換の変更

  • id/AnyObject型のプロパティは非推奨になりました。ほとんど利用されておらず、便利であるユースケースも少ないことと、他のプラットフォームで未サポートであることが理由です。
  • -[RLMArray addNotificationBlock:]-[RLMResults addNotificationBlock:]のブロックは今回から追加のパラメータをとるように変更されました。先述のFine-Grained Notificationsに関する変更です。

不具合の修正

  • RLMObjectのAssociated objectのdeallocメソッドで通知を解除していた場合に解放済みのオブジェクトにアクセスしてしまう問題を修正しました。
  • Realmファイルをオープンする際のわずかなメモリリークを修正しました。
  • Realmの初期化、およびコミット時に十分なメモリ空間を確保できないときに、クラッシュするのではなくRLMErrorAddressSpaceExhaustedエラーを返すように変更しました。

お読みいただきありがとうございます。 Realm で素晴らしいアプリケーションを作りましょう!お困りの際はStack Overflow(日本語)Slack(日本語)Twitter(日本語)GitHub(英語)でご相談ください。

Read more

Realm Java 0.89 — ModelとCollectionのための新インターフェース

by /

Realm Javaの最新版をwebサイトJCenterでリリースしました。

このリリースには、数多くの新機能とバグフィックスが含まれています。

RealmModelインターフェース

Realmのリリース以来ずっと存在していた制限の一つに、RealmのすべてのModelクラスはRealmObjectを継承しなければならないというものがありました。

0.89のリリース以降この制限は緩和され、代わりにRealmModelインターフェースを実装することを選択できるようになります。

@RealmClass
public class implements RealmModel {

}

その場合、RealmModelインターフェースの実装に加えて@RealmClassアノテーションを付与することも必要になります。これは、インターフェースに付与されたアノテーションは、その実装クラスに対しては引き継がれないという現状の制限によるものです。

RealmObjectが持っていた各種メソッドについてはRealmObjectのstaticメソッドとしても提供されるようになったので、RealmModelを使用する際はこちらのstaticメソッドをご利用ください。

Person person = getPerson();

// Extending RealmObject
person.isValid();
person.addChangeListener(listener);

// Implementing RealmModel
RealmObject.isValid(person);
RealmObject.addChangeListener(person, listener);

RealmObjectを継承する方法は依然としてRealmが推奨する方法ですが、開発するアプリケーションのアーキテクチャやコードのガイドラインにとって適した方法を選択してください。

Realm Javaは、モデルクラスがRealmObject以外のクラスを継承してポリモーフィズムを実現することについては依然としてサポートしていません。この機能については、issue761として扱われます。

RealmCollection API

Realm Javaにはコレクションを扱うAPIとしてRealmResultsRealmListの2つのクラスが存在していて、それぞれ標準のListインターフェースを実装しています。

これらのクラスはRealmに関連する機能を持っていますが、その挙動が微妙に異なってていて、RealmBaseAdapterでこれらを共通に扱おうとする際に問題になっていました。

より一貫性のあるAPIとするため、RealmCollectionOrderedRealmCollectionの2つのインターフェースを導入します。

これらのインターフェースにより、APIがもつ名前と振る舞いの一貫性が高まります。

今回の変更では特に以下の点にご注意ください。

removeclearが用いられるAPIはコレクションからの要素の削除のみを意味し、Realmのオブジェクト自体の削除は行われません。これは特にRealmResultsの振る舞いを大きく変えることになります。RealmResultsがもつこれらのメソッドについては、コレクションからの要素の削除が行えないためUnsupportedOperationExceptionをスローします。

  • RealmBaseAdapterRealmListRealmResultsの両方に対して使用できるようになります。

  • オブジェクトをコレクションとRealmの両方から削除するためにはdeleteFromRealm()またはdeleteAllFromRealm()を使用してください。

  • Realm.clear(Class)Realm.clear()はそれぞれRealm.delete(Class)Realm.deleteAll()に変更されます。

  • RealmObject.removeFromRealm()RealmObject.deleteFromRealm()へリネームされました。

安定イテレーター

Realmの重要な機能のひとつに検索結果の自動更新があります。多くの場合この機能は素晴らしいものですが、RealmResultに含まれるオブジェクトに対し、RealmResultsが保持する条件を満たさなくなるような変更を行うケースでは困った問題が発生します。

以下に具体例を示します。

RealmResults<Person> results = realm.where(Person.class).equalTo("inviteToGoogleIO", false).findAll();

// 全員を招待したい
realm.beginTransaction();
for (int i = 0; i < results.size(); i++) {
	results.get(i).inviteToGoogleIO(true);
}
realm.commitTransaction();

通常のコレクションであれば、すべての人がGoogle I/Oに招待されることになります。しかしRealmResultsのライブアップデートの機能のために実際には半分の人しか招待されません。というのも、RealmResultsがライブアップデートされることにより、1つ目のオブジェクトのフィールドがtrueにセットされた瞬間にRealmResultsが更新され先頭の要素が取り除かれてしまいます。このことによりループのインデックスと実際のリストの中身に不整合が発生し、ループが回るたびにひとつオブジェクトを飛ばしてしまいます。

この問題を回避するためにはリストを後ろから更新していく方法がありました。

for (int i = results.size() - 1; i >=0; i--) {
	results.get(i).inviteToGoogleIO(true);
}

この方法は直感的ではありません。0.89からはRealmResultsのライブアプデート機能の動作動作タイミングが変更されています。いままでは同じスレッドで更新があると即座に反映されていましたが、0.89以降では更新タイミングがつぎのLooperイベントのタイミングまで遅延されます。

この変更によりループやイテレーターは意図通りに動作するようになりますが、負の側面として削除済みのオブジェクトに意図せずアクセスしてしまう可能性があげられます。

RealmResults<Person> results = realm.where(Person.class).findAll();
for (Person p : results) {
	p.deleteFromRealm(); // 間接的にすべてのオブジェクトを削除します
}

// RealmResultsは次のLooperイベントまで更新されないため、
// 更新されるまでの間削除済みのオブジェクトを返します。
Person p = results.get(0);
p.isValid() == false;

// RealmResultsから直接削除した場合はそのRealmResultsからも取り除かれます
results.deleteFromRealm(0);

// さらに、新たに検索した場合も、削除が反映されます。
results = realm.where(Person.class).findAll(); // 削除されたユーザーは含まれません

詳細はこちらを御覧ください。

またこの変更は同じスレッド上で発行されたRealm.commmitTransaction()によって実行されるすべてのRealmChangeListenerにも影響します。これらのリスナーは他のスレッド上での変更の場合と同じように、次のLooperイベントまで遅延されます。この変更はほとんどのアプリケーションでは影響がありませんが、たとえば変更の通知が即座に行われることを前提としているユニットテストに影響する可能性があります。

互換性のない変更点 🚨

プライマリーキーのフィールドは、暗黙的な@Requiredとは扱われなくなります。 また、プライマリーキーとして指定されたフィールドがnullを 許容する型の場合、nullを代入することも許されるようになりました。

0.89より前のバージョンからアップデートすると、この変更によりプライマリーキー指定がnull可能な型に付与されていた場合にRealmMigrationNeededExceptionがスローされます。モデル定義に@Requiredを付与して以前と同じ挙動を維持するか、マイグレーションによって以下のようにnullを許可するようにデータベースを変更するかのいずれかの対処を行ってください。

RealmObjectSchema personSchema = schema.get("Person");
personSchema.setNullable("myPrimaryKey", true);

そのほかの改善点

完全な変更内容一覧と詳細はchangelogら参照することができます。


お読みいただきありがとうございます。Realmが素晴らしいアプリの実装のお役に立てれば嬉しく思います。お困りの際はStack Overflow(日 本語)Slack(日本語)Twitter(日本語)GitHub(英語)でご相談ください。

Read more

Realm Cocoa Converter

by /

このたび、Realm Cocoa Converterをオープンソースのライブラリとしてリリースしました!Realm Objective-CとRealm Swiftに足りない部分を補完する役割があります。

これまで開発者のみなさまから、もっと簡単に既存のデータをRealmに変換したい、それをするために開発のかなりの時間を費やしている、との声をたくさんいただいていました。Realm Cocoa Converterはさまざまなデータ形式のファイルからRealmにデータをインポートする、あるいはRealmから別のファイル形式にデータをエクスポートする機能を提供するライブラリです。

このライブラリはSwiftで書かれており、そしてとても簡単に使うことができます。例えばRealmからデータをエクスポートする場合は、あるクラスを1つ使用するだけです。下記の例はRealmのデータをCSVファイルにエクスポートします。

let path = ... // 対象のRealmファイルへの絶対パス
let destinationFolder = ... // CSVファイル(複数)が保存されるフォルダへの絶対パス

let exporter = CSVDataImporter(realmFilePath: path)
exporter.exportToFolderAtPath(outputFolderPath: destinationFolder)

Realmへデータをインポートするには、エクスポートの時よりも少し手順が増えます。Realmはファイルをインポートする際にそれぞれのデータ項目の型を決めて、スキーマを生成する必要があるからです。

Realm Cocoa Converterはスキーマを専用のクラスによってとても賢く生成します。生成されたスキーマはそのまま保存することもできますし、データをインポートする前にカスタマイズすることも可能です。

生成されたスキーマは、インポートを行う際に渡します。

var filePaths = [String]() // Realmにインポートされるファイルパスの配列

// スキーマジェネレータのインスタンスを生成し、インポートするデータファイルを解析します
let generator =  ImportSchemaGenerator(files: filePaths)
let schema = try! generator.generate()

let destinationRealmPath = ... // データがインポートされたRealmファイルの保存場所

// データインポータのインスタンスを生成し、データのインポートを実行します。スキーマは上記で生成したものを渡します。
let dataImporter = CSVDataImporter(files: filePaths)
try! dataImporter.importToPath(String(destinationRealmPath), schema: schema)

Realm Cocoa ConverterはSwiftを用いて記述されています。そしてObjective-Cから利用する場合でも、適切なインターフェースが提供されるようにコード規約を設計しています。

NSString *path = ... // 対象のRealmファイルへの絶対パス
NSString *destinationFolder = ... // CSVファイル(複数)が保存されるフォルダへの絶対パス

RLMCSVDataExporter *exporter = [[RLMCSVDataExporter alloc] initWithRealmFileAtPath:realmFilePath];
[exporter exportToFolderAtPath:destinationFolder withError:nil];

現在はRealmファイルへデータをインポートすることができるファイル形式はCSVとExcel(XLSX)ファイルをサポートしています。Realmファイルからエクスポートできるデータ形式はCSVをサポートしています。今後のバージョンアップにて、その他の形式、JSONやSQLiteなどのサポートも予定しています。

Realm Cocoa Converterはまだリリースしたばかりのライブラリです。まだまだこれからたくさんの改善が必要です。ぜひ開発を手伝ってください!また、具体的なユースケースや、対応してほしいファイル形式がありましたらぜひ私たちに知らせてください!


お読みいただきありがとうございます。Realmがすばらしいアプリを作るお役に立てたなら幸いです。お困りの際はStack Overflow(日本語)Slack(日本語)Twitter(日本語)GitHubでご相談ください。

Read more

Realm Java 0.88 — Better Objects!

by /

Realm Javaの最新版をwebサイトMavenリポジトリでリリースしました。

このリリースには、数多くの新機能が含まれています。

Better Objects!

本リリース以降、Realm Javaのモデルクラスに課せられていた多くの制限事項が取り除かれ、定義の自由度が増します。具体的にはモデルクラスは以下のことができるようになります。

  • 任意のメソッドを追加できるようになります
  • 任意のインタフェースを実装することができます
  • getterとsetterの中に、自由にロジックを組み込むことができます
  • getter/setterメソッドの名前を自由に決めることができます
  • フィールドをpublicにすることで、getter/setterなしにフィールドへアクセスすることができます
  • 独自のtoString()equals()hashCode()メソッドを定義することができます
  • Lombokとともに使用することができます

これらの制限緩和により、今後はRealmObjectをより通常のPOJOと近い感覚で利用できるようになります。

これを実現するため、いままでのライブラリを依存に追加する方法に代わって使用する新たなGradleプラグインを開発しました。 以前のブログポストで述べたように、Realmを利用する際はbuild.gradleでのRealmの設定方法を変更する必要があります。

buildscript {
 repositories {
    jcenter()
 }

 dependencies {
    classpath 'io.realm:realm-gradle-plugin:0.88.0'
 }
}

apply plugin: 'com.android.application'
apply plugin: 'realm-android'

そしてこのことは、今後Gradle以外のビルドシステムではビルドできないことを意味しています。 もしこのことで不都合が生じる場合は、以下の2つのissueでMaven/Antサポートの要望をあげてください。

いただいた要望を参考に、AntとMavenのプラグイン開発のプライオリティの議論を進めていきます。

モデルクラスに対するいくつかの制限は依然として残っていることにご注意ください。

  • RealmObjectクラスを継承しなければならない
  • フィールドをfinalvolatiletransientにすることができない

その他の変更点

今回のリリースでは、その他にも多くの改善が行われています。

  • RealmQuery.distinct()RealmQuery.distinctAsync()が追加されました
  • RealmQuery.isNotEmpty()String型、RealmList型、byte[]型のフィールドに対して使用できるようになりました
  • ネイティブコードをロードする際Relinkerを使用することで、アプリインストールに関連する問題を解決しアプリクラッシュの可能性を減らしました
  • Realm.deleteAll()メソッドが追加され、対象Realm中のすべてのオブジェクトを簡単に削除できるようになりました

互換性のない変更点 🚨

  • すべての変更通知にLooperを使用する���う���なりました。これまでは、トランザクションをコミットする際、コミットしたスレッドと同じスレッド上のRealmChangeListenerは即座にコールバックの呼び出しが行われていました。リスナーの挙動を予測可能かつ一貫性のあるものにするため、同一スレッドで登録されたリスナーの呼び出しについても、次のLooperのメッセージ処理まで遅延されます。

  • すべてのRxJavaのObservableは、関連するRealmインスタンスへの参照を保持します。このことにより、完全にRealmをクローズするためにはすべてのObservableをunsubscribeすることを必要とします。またこのことにより、アプリケーションのコードでは、doOnUnsubscribeでRealmインスタンスをクローズしても問題なく動作するようになりました。

完全な変更内容一覧と詳細はchangelogから参照することができます。


お読みいただきありがとうございます。Realmが素晴らしいアプリの実装のお役に立てれば嬉しく思います。お困りの際はStack Overflow(日本語)Slack(日本語)Twitter(日本語)GitHub(英語)でご相談ください。

Read more

Realm React Nativeを公開

by /

本日のFacebook React.jsカンファレンスにおいて、React Native向けのRealmデータベースを新たに公開いたしました。使いやすいAPIによるオブジェクトの永続化とフル機能のクエリを、他のライブラリに対して2倍から10倍の速度で提供します。

他の言語向けのRealmと同様、使いごこちの良いアプリケーションの開発を可能にするためReact Native向けに設計されたもので、ライブオブジェクト変更通知イベント一方向のデータフローをサポートします。

React Native版のコードは以下のようになります。

const Realm = require('realm');

class Person {}
Person.schema = {
    name: 'Person',
    primaryKey: 'name',
    properties: {
        name: 'string',
        age: {type: 'int', default: 0},
    },
};

const realm = new Realm({schema: [Person]});

// 問い合わせ
let people = realm.objects('Person', 'age >= 17');
people.size // => 0

// 書き込み
realm.write(() => {
    savedPerson = realm.create('Person', {
        name: 'Hal Incandenza',
        age: 17,
    });
});

// 問い合わせ結果は自動的に更新されます
people.size // => 1

Realm はモバイルアプリケーションのために作り上げられたデータベースであり、携帯電話、タブレット、ウエアラブル端末上で動作します。2014年以来RealmはJava版、Objective-C版、Swift版をリリースし、LINE、Yahoo、サイバーエージェント、Wantedly、日経新聞、Sansan、マネーフォワード、Starbucks、Cisco、Walmart、Google、Amazon、eBayをはじめとするさまざまな企業のアプリケーションに利用され、数億台のデバイスで稼働しています。

本日、Facebook社が提供するJavaScriptフレームワークでありiOSおよびAndroidのネイティブアプリケーションの作成を可能にするReact Nativeへの対応を開始したします(React Native であり、Reactフレームワーク全体への対応ではないことにご注意ください)。

Realm React Native は先進的な設計と簡潔さをアプリケーションにもたらし、単一のコードベースでiOSとAndroidの両方に対応することを可能にします。本日一般公開されるRealm React Nativeではありますが、Realm React Nativeは2ヶ月以上前からTaskRabbitのプロダクトコードで使用されています

Realmについて

RealmはSQLite上に構築されたいわゆるORMではありません。我々はモバイルアプリケーションのために1からデータベースを開発しています。そのため、単なるキーバリューストアでではなく、データベースエンジンが保持するデータに動的に対応付けられたネイティブなJavaScriptオブジェクトを提供します。このことにより、Realmは簡潔なAPIとパフォーマンスの両立を達成しています。Realmを用いることで、複雑なデータのモデリング、オブジェクト間のリンク、高度なクエリのすべてが可能となります。

class Dog {}
Dog.schema = {
    name: 'Dog',
    properties: {
        name: 'string',
        age: 'int',
    }
};

let realm = new Realm({schema: [Dog]});

realm.write(() => {
    realm.create('Dog', { name: 'Rex', age: 3 });
});
// 基本的なクエリ
let r = realm.objects('Dog').filtered('age < 8');

// クエリの連鎖
let r2 = r.filtered('name contains "Rex"');
r2.size // => 1

realm.write(() => {
    realm.create('Dog', { name: 'Rex Maximus', age: 4 });
});

// 結果は自動的に更新されます
r2.size // => 2
class Person {}
Person.schema = {
    name: 'string',
    dogs: {type: 'list', objectType: 'Dog'},
};

let realm = new Realm({schema: [Dog, Person]});

realm.write(() => {
    let person = realm.create('Person', {
        name: 'Tim',
        dogs: [{name: 'rex', age: 3}],
    });
});

APIの使用方法に関するさらなる例はサンプルプロジェクトや、テストコードを参照してください。

Realmを使うべき理由

使い方が簡単

上記のサンプルコードを見ていただければ分かる通り、Realmは使いやすさを最も重視しています。これはRealm React Nativeでも変わりありません。使いやすさにつづき、以下の点についても他のプロダクト同様注力しています。

速い!

Realmの使いやすさはパフォーマンスを犠牲にしません。メモリマッピング、遅延ローディング、独自のストレージエンジンなどにより、RealmはリッチなオブジェクトベースのAPIを提供しているにもかかわらず、通常SQLiteやAsyncStorageよりも高速に動作します。それぞれのユースケースでの確認を行うことをおすすめしていますが、多くの場合既存のコードをRealmに対応させることで大幅な高速化を達成できます。詳細はベンチマーク結果を参照してください。

クロスプラットフォーム

Realm React NativeのAPIはJavaScriptで記述できるので、iOSとAndroidの両方を対象とすることができます。さらに、Realmのデータベースファイルフォーマットは完全にクロスプラットフォーム対応なので、iOSとAndroid間で簡単に同じファイルを使用することができます。デバッグの際は、Realm Browserを用いてファイルの中身を確認することができます。

先進的

Realmオブジェクトはつねに最新の状態に維持され、そのことがリアクティブパターンや一方向データフローを当たり前のものにします。リンクの機能はグラフ構造のRealmオブジェクトの扱いを可能にし、クエリ言語により任意のプロパティの組み合わせに対してクエリを発行することができます。また、Realmのデータを簡単にReact Native ListViewに表示させることができます。

信頼性

Realm React Nativeは世界中の数億のユーザーに利用されているRealm Java、Objective-C、Swiftと同じエンジンの上に構築されています。これらはeコマースアプリケーション、銀行のアプリケーション、ヘルスケア分野、さらには政府機関のアプリケーションにも採用され、高い信頼を獲得しています。Realm React NativeはすでにTaskRabbitの公式なアプリケーションで昨年の12月から使用されています

コミュニティドリブン

Realm React Nativeは、GitHub上で開発が行われています。機能はユーザーの要求に応じて優先順位付けされます。また、コードへのコントリビューションも歓迎しています。

サポート

Realmはサポートとバグフィックスを他のすべてのタスクよりも優先します。そのため、データベースに関する質問には直接その開発やメンテナンスを行っている開発者から回答を得ることができます。 質問はSlack(日本語)Twitter(日本語)Stack Overflow(日本語)GitHub(英語)で受け付けています。

テストは2016年2月19日時点の最新版のRealm、SQLiteはReact Native SQLite Storageを、AsyncStorageはReact Native Storeを使用しています。また、測定はiPhone 6s(iOS 9.2.1)とNexus 9(Android 5.0.1)上でベンチマークプログラムにより行っています。

今後について

本日のRealm React Nativeが提供する機能は、Realm Java、Objective-C、Swiftが現在提供している機能とくらべると若干不足している部分があります。しかし、TaskRabbitの例のように、実際にプロダクトに投入するレベルの信頼性をすでに獲得しているのも事実です。我々が今後どのようにRealm React Nativeを進化させていくべきかについて、みなさんのフィードバックを必要としています。バグレポートや機能追加リクエストをGitHubにお寄せください。APIに関しては、今後数週間の間に大きく改善される予定です。特に現状不足しているマイグレーション関連やクエリ関連に注力します。

もしJavaScriptは使用しているがReact Nativeは使用していないという場合であってもがっかりしないでください。今後、Cordova/PhoneGap/Ionicのサポートを計画しています。さらにはNode.js(V8)互換性も将来的には視野に入れています。

Realmがどのようなプロダクトに使われることになるのか、我々はとても楽しみにしています。

TaskRabbitとReact Nativeチームのサポートに大変感謝しています。彼らのフィードバックのおかげでRealm React Nativeはとても素晴らしい物になりました。また、Realm React NativeをReact Conf 2016で発表できることを大変光栄に思っています!

Read more

Realm Objective-C & Swift 0.98

by /

Realm Objective-CおよびRealm Swift 0.98をリリースしました。

このバージョンにはコレクションの変更に対する通知、非同期クエリ、サブクエリ、インデックスとパフォーマンスの改善、不具合の修正などが含まれます。

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

コレクションオブジェクトが変更されるたびに呼ばれる通知ブロックをaddNotificationBlockメソッドを用いて登録できるようなりました。

コレクションインスタンスに対して通知を登録すると、非同期にクエリを実行し、結果を取得したらブロックを呼び出します。その後、結果に影響のあるトランザクションがコミットされるたびに通知ブロックを呼びます。

// RLMResultsの通知を監視します
self.token = [[Person objectsWhere:@"age > 5"] addNotificationBlock:^(RLMResults *results, NSError *error) {
    // results is identical to '[Person objectsWhere:@"age > 5"]'
    [myViewController updateUI];
}];

// 通知が不要になったら
[self.token stop];
// RLMResultsの通知を監視します
let token = realm.objects(Person).filter("age > 5").addNotificationBlock { results, error in
    // results is identical to 'realm.objects(Person).filter("age > 5")'
    viewController.updateUI()
}

// 通知が不要になったら
token.stop()

詳しくはドキュメントの通知のセクションと下記のAPIドキュメントをご覧ください:

Objective-C Swift
-[RLMRealm addNotificationBlock:] Realm.addNotificationBlock(_:)
-[RLMCollection addNotificationBlock:] AnyRealmCollection.addNotificationBlock(_:)
-[RLMResults addNotificationBlock:] Results.addNotificationBlock(_:)
-[RLMArray addNotificationBlock:] List.addNotificationBlock(_:)
-[RLMNotificationToken stop] NotificationToken.stop()

これはキー値監視から始まった基礎的な部分の上に成り立っています。より細かな通知ができるように近い将来の改善を予定しています。

バックグラウンド・クエリ

コレクションの変更に対する通知は、クエリの実行結果が常に「最新の状態」を保つ仕組みを改善しました。

一度クエリが実行される、または通知ブロックが登録された場合、クエリの実行結果は常に最新の状態が保たれます。Realmに変更があったとき、可能ならバックグラウンドスレッドでクエリを実行します。

この変更はResultsオブジェクトを直接テーブルビューのデータソースとして利用する場合の作業を減らします。これはRealmの使い方として推奨するデザインパターンです。

サブクエリ

このバージョンからサブクエリをサポートしました。これまで不可能だったり効率が悪かったクエリを実行できるようになりました。

下記の例は、以前のバージョンでパートタイムで(=フルタイムでない)30歳以上のEmployeeオブジェクトを検索します:

let partTimeOverThirty = realm.objects(Employee).filter("age > 30 AND fulltime = NO")
let companies = realm.objects(Company).filter("ANY employee IN %@", partTimeOverThirty)

しかし、上記のクエリは非常に実行コストが高く、また自動更新が期待するように動作しません。また、件数をゼロ以外の値と比較することが困難です。

サブクエリを使うとこのように書けます:

let companies = realm.objects(Company).filter(
  "SUBQUERY(employees, $employee, $employee.age > 30 AND $employee.fulltime = NO)[email protected] > 0"
)

しかしながら、今のところサブクエリには下記の制限があります:

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

インデックスとパフォーマンスの改善

BOOL/BoolNSDate型のプロパティに対してインデックスが使えるようになりました。Realm SwiftにおいてOptional型のプロパティが正しくインデックスされるようになりました。

1つ以上のインデックス付きのプロパティを含むオブジェクトの削除のパフォーマンスが大幅に向上しました。

非互換の変更

  • Realm SwiftにおけるSwift 1.2のサポート終了しました。Swift 1.2をサポートする最後のバージョンは0.97.1になります。
  • +[RLMRealm realmWithPath:]/Realm.init(path:)はデフォルトのRealmコンフィギュレーションの値を引き継ぐようになりました。

細かな改善

バージョン0.97.1に含まれる変更です。

  • Swift: Error enumを追加し、エラーをキャッチできるようになりました。例: RLMRealm/Realmインスタンスの初期化時に発生するエラー。
  • 読み込み専用としてRealmを開こうとしてファイルが見つからなかった場合、あるいはあるいはコピー先のディレクトリが見つからなかった場合に、より一般的なRLMErrorFileAccessエラーではなく、RLMErrorFileNotFoundを投げるようになりました。

不具合の修正

バージョン0.97.1に含まれる変更です。

  • -[RLMResults setValue:forKey:]をそのキーで検索して得られたRLMResultsに対して使用したとき、間違った結果を返す、またはクラッシュする不具合を修正しました。
  • 別のスレッドで解放されたRLMRealmにアクセスしてクラッシュする不具合を修正しました。
  • 削除されたオブジェクトを含むResults/RLMResultsに対して集計関数を使用すると間違った結果を返す、またはクラッシュする不具合を修正しました。
  • 100万件を超えるオブジェクトが保存されているクラスに新しくプロパティを追加するとクラッシュする不具合を修正しました。
  • writeCopyToPathによって作られた暗号化Realmを開く際に起こるエラーをを修正しました。
  • 関連を条件にしたクエリを実行して得られたRLMResultsに対して、その関連が削除された後、自動更新が働くと、間違った結果を返す、またはクラッシュする不具合を修正しました。

お読みいただきありがとうございます。 Realm で素晴らしいアプリケーションを作りましょう!Stack OverflowGitHubTwitterでお待ちしています。

Read more

Realm Javaのセットアップ方法が変わります

by /

これまでのRealmの制限事項

Realm for Androidをリリースしてからのこの1年、ライブラリにはいくつかの制限があるにもかかわらず高い評価をいただきました。

Realm for Androidの制限事項の中でも、RealmObjectを継承したクラスに課せられる以下のような制約に対する多くのご要望をいただきました。

  • publicなフィールドを持つことができない
  • アクセッサのメソッド名が標準的なものに限られている
  • アクセッサに独自のロジックを持つことができない
  • カスタムメソッドを持つことができない
  • メソッドを持つinterfaceを実装することができない

これらの制限事項はある共通の理由に由来しています。それは、Realmがデータストアに対するzero-copyオペレーションを実現するためにproxyクラスを使用しているということです。

それでは、これらの制限事項を一つずつ見ていきましょう。

publicなフィールドを持つことができない点について

Java言語はフィールドに対するアクセスを横取りすることを可能にする言語仕様を持っていないので、フィールドに対するアクセスに対してRealmのproxyクラスが割り込むことができません。また、フィールドの情報はJavaヒープ上に保持されることが言語仕様で定められています。このことにより、フィールドへの直接アクセスを許してしまうとRealmの情報をフィールドにコピーしなければならないことになり、zero-copyではなくなってしまいます。

アクセッサのメソッド名が標準的なものに限られている点について

アノテーションプロセッサはとても強力なツールですが、いくつかの制限も存在しています。そのうちの一つに、proxy対象のクラスのフィールドやメソッドの名前は取得できるが、メソッド内の処理内容を取得できないというものがあります。そのため、Realmのproxyクラスが意図通りに動作するかは利用者が規約に従った実装を行っているかに依存します。

アクセッサに独自のロジックを持つことができない点について

これは一つ前の項目から導かれる制限です。アノテーションプロセッサはアクセッサの実装内容を取得することができないので、proxyクラスにその実装内容を取り込むことができません。もちろん単にsuper.getMyField()を呼び出すことはできますが、この呼び出しはメモリー上のデータに対する操作であり、proxyクラスが対象とするRealmのデータを対象とするものにはなりません。

カスタムメソッドを持つことができない点について

繰り返しになりますが、アノテーションプロセッサはコードの内容を取得することができないので、カスタムメソッドが実装に行っている処理を把握することができません。このようなメソッドを許してしまうと正しく動作することを保証できなくなってしまうため、Realmはそのようなメソッドを許容していません。

メソッドを持つinterfaceを実装することができない点について

カスタムメソッドを許容しないという制限により、メソッドの実装が必要なinterfaceについては許容されません。

制限事項の解消に向けて

これらの制限事項を解消する唯一の方法は、バイトコード変換を使用することです。これは、ソースコードのコンパイルで生成された.classファイルに対して加工を行うことを意味しています。どのような加工を行うかについては別の記事での解説を予定しているので、ここでは詳細については割愛します。重要なことは、Gradleプロジェクトでバイトコード変換を行うためには、Gradle pluginが必要であるということです。これはRetroLambdaHugoなどが行っている方法です。

build.gradleの記述方法の変更

これまでは、以下のように設定することでRealmを使用していました。

repositories {
    jcenter()
}

dependencies {
    compile 'io.realm:realm-android:<version>'
}

これを、以下のようにRealmのGradleプラグインを使用する方式に書き換える必要があります。

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'io.realm:realm-gradle-plugin:<version>'
    }
}

apply plugin: 'realm-android'

大部分のコードはすでにbuild.gradleにすでに記述があると思うので、classpathapply plugin: 'realm-android'の行を追加してください。

なぜ今変更するのか

なぜまだバイトコード操作方式がリリースされてないこのタイミングでプラグインを提供しはじめるのでしょうか?

これにはバイトコード操作以外にも理由があるからです。

APKサイズの削減!

プラグインを使用することで、AAR形式のRealmを簡単に使用することができるようになります。また、アノテーションプロセッサのクラスがライブラリ本体から分離されます。このことは、生成されるAPKファイルにはアノテーションプロセッサのクラスが含まれなくなることを意味し、数Kバイトではありますが、サイズが削減できます。

ABI splitsを簡単に!

AAR形式のRealmを利用できるようになる事の別の利点として、これまでABI splitsを使用する際に必要であったワークアラウンドが不要になります。これにより、すべてのCPUアーキテクチャ用のネイティブライブラリが含まれた巨大なAPKで配布する必要はなくなります。

Eclipseはどうなるのでしょうか?

おそらくご存知だ思いますが、GoogleはADT plugin for Eclipseを非推奨としてすでにサポートを終了しています。そのため多くのAndroid開発者はすでにAndroid Studioへの移行を完了しており、我々が注力すべきもそちらであると考えています。 ただし、何を優先すべきかは開発者の皆様からのフィードバックによって決まります。そのため、AntやMavenのpluginに対する多くの要望が集まるようであれば、優先度ついて再検討を行う可能性があります。

Happy Coding!

この変更により、開発者の皆様により快適に開発を行っていただけるものと期待しています。ご意見やご要望がありましたらSlack(日本語)Twitter(日本語)Stack Overflow(日本語), GitHub(英語)で遠慮なくお知らせください。

Read more

Realm Java 0.87 — RxJavaサポート!

by /

Realm Javaの最新版をwebサイトMavenリポジトリでリリースしました。

このリリースでは、多くの要望をいただいていたRxJavaサポートが導入されました。

RxJava

RxJavaは、NetflixがリリースしているReactive Extensionsライブラリで、Observerパターンを拡張したものです。RxJavaはデータに対する変更を監視するとともに、様々な処理を組み合わせてデータに適用することを可能にします。

Realmが提供するObservableは、従来提供されていたRealmChangeListenerの仕組みの上に構築されています。そのため、今までRealmChangeListenerが受け取っていたコールバックと同じタイミングでObservableに対する呼び出しが行われます。

Realm realm = Realm.getDefaultInstance();
RealmResults<Person> persons = realm.where(Person.class).findAll();
Person person = persons.first();

Observable<Realm> realmObservable = realm.asObservable();
Observable<RealmResults<Person>> resultsObservable = persons.asObservable();
Observable<Person> objectObservable = person.asObservable();

Realmが提供するAPIには非同期のものもありますが、同様に利用可能です。

realm.where(Person.class).equalTo("name", "John").findAllAsync().asObservable()
  .filter(new Func1<RealmResults<Person>, Boolean>() {
      @Override
      public Boolean call(RealmResults<Person> persons) {
          // 完了していない場合は無視する
          return persons.isLoaded();
      }
  })
  .subscribe(new Action1<RealmResults<Person>>() {
      @Override
      public void call(RealmResults<Person> persons) {
          // personsを表示
      }
  });

様々な例が、RxJava sample projectに収録されています。

RxJavaに関する設定

RxJavaは必須の依存ライブラリではありません。これは、Realmが自動的にRxJavaのライブラリを依存ライブラリに含めてはくれないことを意味しています。Realmを利用するアプリでどのバージョンのRxJavaを使用するか決めることができます。また、RxJavaを含めないようにすることで、メソッド数の増加を抑えることも可能です。

RxJavaを使用する場合は、build.gradleに依存ライブラリとして明示的に追加してください。

dependencies {
  compile 'io.realm:realm-android:0.87.0'
  compile 'io.reactivex:rxjava:1.1.0'
}

Observableインスタンスの生成方法をカスタマイズ可能にするために、RxObservableFactoryインタフェースが追加されています。 このカスタムファクトリは、RealmConfigurationで指定することができますが、指定されない場合はRealmObservableFactoryクラスがデフォルトのファクトリクラスとして使用されます。この場合、RxJava 1.1.*をサポートします。

RealmConfiguration config = new RealmConfiguration.Builder(context)
  .rxFactory(new MyFactory())
  .build()

Work-in-progress

Realm Java 0.87.0はRxJavaをサポートする最初のバージョンですが、RealmQueryRealmListに対するObservableサポートについては今回のバージョンでは提供されていません。これらについては将来のバージョンでサポートすることを予定しています。

また、RealmObjectRealmResultsは依然としてスレッドをまたいだ利用は行えません。

// Observableでよくあるバックグラウンドスレッドの使い方
Observable.defer(new Func0<Observable<RealmResults<Person>>>() {
    @Override
    public Observable<RealmResults<Person>> call() {
        return realm.where(Person.class).findAll().asObservable();
    }
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Action1<RealmResults<Person>>() {
    @Override
    public void call(RealmResults<Person> persons) {
        // personsにアクセスするとスレッド制約違反でクラッシュします
    }
});

// 正しくは、Realmが提供する非同期APIを使用します
realm.where(Person.class).findAllAsync().asObservable()
  .subscribe(new Action1<RealmResults<Person>>() {
      @Override
      public void call(RealmResults<Person> persons) {
          // UIにデータを表示
      }
  });

RealmObjectRealmResultsはライブなオブジェクトなので、Realmの更新に伴って最新の値に更新され続けます。これはデータの一貫性という点でとても素晴らしいことですが、RxJavaのイミュータブルなスレッドセーフオブジェクトと相性が良い設計とは相容れない部分があります。そこで、RxJavaサポートの一環としてRealm.copyFromRealm()メソッドを追加しました。このメソッドは、ライブなRealmObjectの内容をRealmから切り離された通常のオブジェクトにコピーします。この処理は処理時間、メモリのいずれの点においても高価ですが、簡単にbuffer()と組み合わせられるようになります。

// 更新される前と後のPersonを扱う例
realm.where(Person.class).findFirst().<Person>asObservable()
        .map(new Func1<Person, Person>() {
            @Override
            public Person call(Person person) {
                // Realmから切り離されたコピーを作成
                return realm.copyFromRealm(person);
            }
        })
        .buffer(2)
        .subscribe(new Action1<List<Person>>() {
            @Override
            public void call(List<Person> persons) {
                // `map`でRealmから切り離していない場合、v1とv2は両方とも最新に更新されてしまいます
                Person v1 = persons.get(0);
                Person v2 = persons.get(1);
            }
        });

このようなコピーを作成するワークアラウンドは将来的には不要になる予定です(issue 1208)。

詳細な変更点については、changelogを参照してください。


お読みいただきありがとうございます。Realmが素晴らしいアプリの実装のお役に立てれば嬉しく思います。お困りの際はStack Overflow(日本語)Slack(日本語)Twitter(日本語)GitHub(英語)でご相談ください。

Read more