Realm Blog

Realm ObjC & Swift 2.3: 同期の進捗を通知、共有機能の改善、バックアップと復元!

Realm Objective‑CおよびRealm Swift 2.3をリリースしました。 このバージョンには同期の進捗の通知、バックアップと復元、より柔軟な共有の仕組みなどRealm Mobile Platformに関する改善が含まれます。詳しくは下記をご覧ください。

同期の進捗の通知

Realm Mobile Platformは完全なオフラインファーストのユーザー体験を提供します。ネットワークの状態に関係なく、ローカルのRealmには即座に変更をいつでも加えることができます。その背景には自動的な同期が常に実行されており、ネットワーク通信が可能になるとすぐに、変更は即座に各デバイスとサーバー間で伝播します。このことにより、リアルタイムな共同作業が可能になります。このようなリアルタイムな共同編集を可能にするという機能が、Realmを差別化する大きな特長です

しかし、データが今どのくらい転送されたのかを知りたいときもあります。それは例えばアプリを最初に起動するときがそうです。画面を表示する前に、サーバーにあるデータがこれからどれくらいダウンロードされるのか確認したいことでしょう。また、もっと単純にデータ同期中にプログレスバーやアクティビティインジケータを表示したいこともあるでしょう。

まさにそれが、今回のリリースで同期の進捗を監視できるAPIを追加した理由です。同期の進捗はSyncSessionに通知ブロックを追加することで行います。シンプルに、データがどっち向きに転送されるか(.uploadまたは.download)と、通知が継続するかワンショットで終わるのか(.reportIndefinitely/.forCurrentlyOutstandingWork)をモードとして指定できます。

例えば、大きいサイズの画像をRealmに保存した際に、それがサーバーにアップロードされるまでのブログレスバーを表示したいときは、次のように書きます。

let session = SyncUser.current.session(for: realmURL)!
self.token = session.addProgressNotification(for: .upload,
                                             mode: .forCurrentlyOutstandingWork) { progress in
  self.updateProgressBar(fraction: progress.fractionTransferred)
  if progress.isTransferComplete {
    self.hideProgressBar()
    self.token.stop()
  }
}

別の例として、Realmの通信中はアクティビティインジケータを必ず表示したいという場合は、次のように書きます。

let session = SyncUser.current.session(for: realmURL)!
self.token = session.addProgressNotification(for: .download,
                                             mode: .reportIndefinitely) { progress in
  if progress.isTransferComplete {
    self.hideActivityIndicator()
  } else {
    self.showActivityIndicator()
  }
}

新しい柔軟な共有の仕組み

リアルタイム共同編集とデータ同期はRealm Mobile Platformを支える重要な柱です。それを実現するためにモバイルデベロッパーに向けて、複数のユーザー間でRealmを共有する仕組みをPermissionChange APIとして11月に導入しました。

本日、より簡単に異なるユーザー間でRealmを共有する仕組みをご紹介します。とてつもなく柔軟な権限の管理を可能にし、サーバーサイドのコードは一切必要ありません。すべてはクライアント側のAPIだけでコントロールできます。具体的には、新しく追加したSyncPermissionOfferSyncPermissionOfferResponseクラスがを使って、権限の変更を要求したり、承認したりして、Realmの共有を実現します。

これらのAPIはRealm Mobile Platformが実現する「APIとしてのオブジェクト」というアプローチのデモンストレーションでもあります。権限の変更を要求したり受け取ることは、以前のPermissionChangeを利用したプロセスとほとんど同じ仕組みです。シンプルに各ユーザーのマネジメントRealmにオブジェクトを保存し、それがサーバーに同期されて処理された結果が返ってくることを通知を用いて監視します。

同期されたRealmを共有するには次のような手順で行いま

  1. SyncPermissionOfferオブジェクトを、ユーザーのマネジメントRealmに保存します。
  2. SyncPermissionOfferオブジェクトがサーバーによって処理されるのを待ちます。
  3. オブジェクトにtokenプロパティが割り当てられると、別のユーザーにトークンを送ります。トークンを送る方法はE-mailやiMessage、共有アクション、伝書鳩など、なんでも好きな方法で構いません!
  4. トークンを受け取ったユーザーはSyncPermissionOfferResponseオブジェクトを自分のマネジメントRealmに保存します。
  5. SyncPermissionOfferResponseオブジェクトがサーバーによって処理されるのを待ちます。
  6. SyncPermissionOfferResponseオブジェクトが処理されると、トークンを受け取ったユーザーは、共有されたRealmを開くことができるようになります。共有されたRealmのURLは、realmUrlプロパティで参照できます。

例:

////////////////
// Sender
////////////////

// Create offer with full permissions
let shareOffer = SyncPermissionOffer(realmURL: realmURL, expiresAt: nil,
                                     mayRead: true, mayWrite: true, mayManage: true)
// Add to management Realm to sync with ROS
try managementRealm.write {
  managementRealm.add(shareOffer)
}
// Wait for server to process
let offerResults = managementRealm.objects(SyncPermissionOffer.self).filter("id = %@", shareOffer.id)
shareOfferNotificationToken = offerResults.addNotificationBlock { _ in
  guard case let offer = offerResults.first,
             offer.status == .success,
             let token = offer.token else {
    return
  }
  // Send token via UIActivityViewController
  let url = "realmtasks://" + token.replacingOccurrences(of: ":", with: "/")
  let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: nil)
  self.present(activityViewController, animated: true, completion: nil)
}

////////////////
// Receiver
////////////////

// Create response with received token
let response = SyncPermissionOfferResponse(token: token)
try managementRealm.write {
  managementRealm.add(response)
}
// Wait for server to process
let responseResults = managementRealm.objects(SyncPermissionOfferResponse.self).filter("id = %@", response.id)
acceptShareNotificationToken = responseResults.addNotificationBlock { _ in
  guard case let response = responseResults.first,
             response.status == .success,
             let realmURL = response.realmUrl else {
    return
  }
  // User can now access Realm at realmURL 🎉
}

Todoリストのデモアプリ、RealmTasksのProof-Of-Conceptブランチにて、この仕組みを用いて、どのようにRealmをユーザー間で共有するのかという原理のデモンストレーションを示しています。

バックアップと復元

もしサーバーがダウンしてしまったら、復元をしなければなりません。この数か月の間、Realm Mobile Platformにバックアップの機能を提供するために開発を続けていた理由です。本日、常に最新のバックアップを自動的に取得する、継続的なバックアップソリューションをご紹介いたします。

通常の状態では、Realmの同期エンジンは実際のデータではなく、操作手順だけを転送します。Realm Object Serverが新しい操作手順の受信を確認すると、ローカルログはクリアされます。これは、アプリのディスク使用量を小さくし、Realmが高速に動作するための仕組みです⚡️。

さらにいうと、Realmはオフラインファーストのデータベースであるので、Realm Object Serverが何らかの理由によりダウンしたとしても、ローカルデータは利用可能なままです。

しかしながら、バックアップからサーバーのデータを復元すると、クライアントはグローバルエラーハンドラーから”client reset”エラーを受信します。その状態でもローカルのRealmは通常と同じように使い続けることができますが、バックアップの後に記録された変更は失われてしまいます。

“client reset”エラーを受信した場合は、ユーザーに対してそのことを通知し、Realmへのアクセスを中止し、サーバーから最新のバックアップを取得しなおしてください。NSErrorオブジェクトのuserInfoプロパティはブロックオブジェクトを保持していて、それを呼ぶことで、ローカルの古いバージョンのRealmをクリアできます。

もし、”client reset”エラーをすぐに処理しない場合は、次回以降の起動時にRealmにアクセスすると、ローカルの古いRealmは削除され、サーバーから最新のバックアップが自動的に再ダウンロードされます。

不具合の修正

  • commitWrite(withoutNotifying:)に関して、トランザクションをコミットした際に、次回以降の通知が間違ってスキップされてしまう問題を修正しました。
  • 同期によってコンフリクトしたオブジェクトをマージした際に不正な結果を生み出し、コレクション通知でクラッシュを引き起こす問題を修正しました。

古い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.

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