Realm Blog

Realm Objective-C & Swift 2.1 – Collectionに対する通知を改善

Realm Objective‑CおよびRealm Swift 2.1をリリースしました。 このバージョンにはコレクションに対する通知に関して、不具合の修正と改善が含まれます。詳しくは下記をご覧ください。

Background (on) Notifications

Realmはデータを常にリアクティブにオブジェクトそのものとして扱うアプローチをとっています。つい最近のRealm Mobile Platformのリリースにより、Realmを使って作られたアプリケーションは、別のスレッド、プロセス、はたまた他のデバイスなど世界中のどこで起こった変更に対しても、非常に簡単に反応することができるようになりました🌏!

コレクションに対する通知の更新情報は継続的に何が追加・削除・変更されたのかを表す操作として配信されます。ORMのように古い値と新しい値の差分を問題にしてるわけではないので、結果を引き起こした操作を正確に再現し、より美しいアニメーションを実現でき、それを簡単なコードで書くことができます。

これらの通知は常に非同期に配信されます。アプリのメインスレッドをブロックしたり妨害することはありません。ただし、変更をメインスレッドで同期的に実行し、即座にUIに反映する必要がある状況も存在します。このようなトランザクションを、インターフェース駆動型の書き込みと呼ぶことにします。

インターフェース駆動型の書き込み

UIスレッドで書き込みを行い、明示的にUIの状態に反映することを「インターフェース駆動型の書き込み」と呼びます。

たとえば、ユーザーがテーブルビューに項目を追加するとします。UIはユーザーがその操作を開始すると同時にアニメーションを伴って反映されるのが理想的です。

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

通知をスキップする書き込み

この問題を解決するために、通知の発生を抑制できる仕組みを用意しました。」

この仕組みを「通知をスキップする書き込み」と呼んでいます。

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

コード例

// Add fine-grained notification block
self.notificationToken = self.collection.addNotificationBlock { [unowned self] changes in
  switch changes {
  case .Initial:
    self.tableView.reloadData()
  case .Update(_, let deletions, let insertions, let modifications):
    // Query results have changed, so apply them to the 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: .None)
    self.tableView.endUpdates()
  case .Error(let error):
    // handle error
  }
}

func insertItem() throws {
  // Perform an interface-driven write on the main thread:
  self.collection.realm!.beginWrite()
  self.collection.insert(Item(), atIndex: 0)
  // And mirror it instantly in the UI
  self.tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Automatic)
  // Making sure the change notification doesn't apply the change a second time
  try self.collection.realm!.commitWrite(withoutNotifying: [self.notificationToken])
}

デモ

RealmTasks Fine-Grained Notifications

オープンソースとして公開されているRealmTasksの、「通知をスキップする書き込み」によりきめ細やかな通知が利用できるようになったPull Requestをご覧ください。macOS版はこちらです。

不具合の修正

以上の機能を実装中に、コレクション通知に関するいくつかの不具合を発見することができました。そのためこのバージョンの通知は以前よりも安定して動作します!

  • 書き込みトランザクションを開始した際に読み取りバージョン更新される場合にもコレクション通知が送られるようになりました。(以前はRealmの通知だけが送られていました。)
  • まれに通知の状態が不整合になってしまう問題を修正しました。
  • レースコンディションを引き起こす不具合を修正しました。
  • トランザクションをキャンセルした場合に通知が送られないように修正されました。

古いSwiftバージョンのサポート

Xcode 7.3.1とSwift 2.2のサポートはできる限り長い間続ける予定ですが、できるだけ早くXcode 8に移行することをおすすめします。


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


Realm Team

At Realm, our mission is to help developers build better apps faster. We provide a unique set of tools and platform technologies designed to make it easy for developers to build apps with sophisticated, powerful features — things like realtime collaboration, augmented reality, live data synchronization, offline experiences, messaging, and more.

Everything we build is developed with an eye toward enabling developers for what we believe the mobile internet evolves into — an open network of billions of users and trillions of devices, and realtime interactivity across them all.

記事の更新情報を受け取る