iCloudでドキュメントを管理するための設計

ドキュメントは、iCloudドキュメントストレージで管理するようアプリケーションを設計すれば、どのデバイスからもアクセスできるようになります。ドキュメント(iOSではUIDocumentクラス、OS XではNSDocumentクラスで管理)とは、関連するデータを集約し、単一のファイルまたはファイルパッケージとしてディスクに保存できるようにしたもののことです。一方、ファイルパッケージとは、ディレクトリを単一ファイルのように見せかけたもので、アプリケーションからはNSFileWrapperオブジェクトを使ってアクセスできます。

ドキュメントには自動的に、iCloudアプリケーションが想定するさまざまな機能が備わります。たとえば、iCloud側でドキュメントに変更があると、自動的にローカル側にも反映されます。これを実現するのがファイルコーディネータ(NSFileCoordinator)オブジェクトであり、ファイルプレゼンタ(NSFilePresenter)プロトコルに準拠しています。OS X v10.8以降、何もしなくてもドキュメントを開く/保存する/名前変更する機能やUIが使えるようになりました。iOSの場合はアプリケーション側で実装しなければなりません。iCloudドキュメントを扱う場合、ファイルコーディネータやファイルプレゼンタの使用は必須です。

iCloudドキュメントストレージの仕組み

ファイルやディレクトリをiCloudドキュメントストレージに書き出すと、システムは自動的に、iCloudや、同じユーザが持っている他のデバイスに転送します。iCloudドキュメントストレージの使い方は、次の点を除き、ローカルファイルシステムと同様です。

iCloudドキュメントストレージの動作を理解するためには、実際に動いている様子を見るのが一番でしょう。図3-1に、デバイス上にあるローカルストレージの概略を示します。アプリケーションは、ローカルデータディレクトリばかりでなく、適切なエンタイトルメントがあればどのiCloudコンテナにもアクセスできます。iCloudコンテナ自体もデバイス上にありますが、ファイルシステムとは独立しているので、あらかじめ設定が必要です。設定にはNSFileManagerURLForUbiquityContainerIdentifier:メソッドを使います。バックグラウンドスレッドから、当該iCloudコンテナを引数として呼び出してください(手順1)。システムはコンテナに必要な設定を施し(手順2)、コンテナディレクトリの基底URLを返します。これをもとに、特定のファイルやディレクトリを指すURLを組み立て、コンテナ内を検索するメタデータクエリを作成します(手順3)。

図3-1 iCloudアプリケーションにおけるデータ/メッセージのやり取り

ファイルコーディネーションを利用してファイルやディレクトリにアクセスする

iCloudアプリケーションが、コンテナ内のファイルやディレクトリにアクセスするためには、ファイルコーディネーションの仕組みを使わなければなりません。これは、ファイルコーディネータおよびファイルプレゼンタというオブジェクトを使って、ファイルやディレクトリに対するアクセスの順序を適切に並べ、データの整合性を保つ仕組みです。ファイルプレゼンタはファイルを監視しています。他のスレッドやプロセスが何らかの操作を施せばその旨が通知され、認識できるようになっています。ファイルに対する操作はすべて、ファイルコーディネータを介しておこなう必要があります。ファイルコーディネータはこの操作を、関係するファイルプレゼンタと調整を取りながら実行します。たとえばあるファイルを別の場所に移動する場合、ファイルコーディネータは移動先の場所を、関係するファイルプレゼンタに通知します。同様に、ファイルを書き出す場合、ファイルプレゼンタがすべて、そうしても安全である旨を表明するまで待つようになっています。

ドキュメントオブジェクト(iOSならばUIDocument、OS XならばNSDocument)を使えば、特に意識しなくても、ファイルコーディネーションを利用していることになります。これには「プレゼンタ」としてのプロトコル(NSFilePresenter)が実装されており、適切なメソッドでファイルやファイルパッケージを管理するようになっているからです。ファイルの内容を読み書きする際にも、ドキュメントオブジェクトは自動的に、ファイルコーディネータ(NSFileCoordinatorクラスのインスタンス)を利用します。一方、ドキュメントオブジェクトを使わずにファイルにアクセスするのであれば、ファイルコーディネーションの機能を、開発者が意識的に組み込まなければなりません。独自に定義したオブジェクトにファイルコーディネーションの機能を組み込む手順については、『ファイルシステムプログラミングガイド』の「ファイルコーディネータとファイルプレゼンタの役割」を参照してください。

iCloudとの間でデータをやり取りする

ドキュメントをディスク保存形式に変換しながら初めてiCloud(遍在)コンテナに書き出すと、システムはファイルまたはファイルパッケージの全体を、iCloudサーバに転送します(図3-2を参照)。最初にドキュメントのメタデータを送信します。ドキュメント名、修正日付、ファイルサイズ、ファイル型などといった情報です。これにはほとんど時間がかかりません。次にドキュメントのデータを送信します。ドキュメントメタデータを先に送信すれば、iCloudは迅速に、新規ドキュメントの存在を認識できます。iCloudサーバは、同じiCloudアカウントに関連付けられたほかのデバイスすべてに、新規および変更後のメタデータを、即座に送信します。したがって各デバイスも、新規ドキュメントの存在を認識します。

図3-2 ファイルを初めてiCloudに転送

ドキュメント本体データがサーバに置かれた後、iCloudはデバイスとの転送処理を最適化します。すなわち、変更のたびにファイルやファイルパッケージ全体を送信する代わりに、メタデータと変更箇所だけを送信するのです。図3-3に、この増分アップロード処理の様子を示します。この場合も、システムはまずファイルのメタデータを送信して、iCloudから他のデバイスに伝達できるようにします。その後システムは、ドキュメントのうち変更された部分のみをアップロードします。したがって、iCloudネットワークのトラフィックだけでなく、デバイスの消費電力も削減できます。増分のみの伝送が可能なようにファイル形式を設計すれば、より効果的でしょう(「ネットワーク伝送効率を考慮した設計」を参照)。

図3-3 変更箇所だけをiCloudに転送

iCloudに新たに追加されたファイルは、そのユーザの他のデバイスにダウンロードしなければなりません(図3-4を参照)。iCloudはまず、メタデータを各デバイスに送信して、ファイルの存在を認識させます。その後、各デバイスがファイルデータを取得するタイミングは、デバイスの種類によって異なります。iOSの場合、アプリケーション側から(明示的に、あるいは暗黙に)システムに対して、ファイルをダウンロードするよう要求しなければなりません。暗黙的にダウンロードするには、NSFileCoordinatorクラスのメソッドを使ってそのファイルへのアクセスを試みます。明示的にダウンロードするには、NSFileManagerクラスのstartDownloadingUbiquitousItemAtURL:error:メソッドを使います。一方、Macは、サーバ上にファイルが存在する旨を認識すると、自動的にダウンロードします。そのためMacは「貪欲な仲間」いう比喩で呼ばれることがあります。Macアプリケーションが新規ドキュメントのメタデータを認識すれば、ドキュメント本体がまだローカルに存在しなくても、実はシステムがアプリケーションに代わってダウンロード済みかもしれません。

図3-4 iCloudから初めてファイルを受信

ファイルの初回ダウンロード後は、差分のみダウンロードすればよいことになります(図3-5を参照)。この場合もやはり、更新されたドキュメントのメタデータをまず送信します。その後、デバイスは自動的に、適切なタイミングで差分を取得します。iOSデバイスの場合、たとえば当該ファイルを所有するアプリケーションがフォアグラウンド状態に移行した時点です。OS Xであれば直ちに取得します。

図3-5 iCloudから更新データを受信

アップロード/ダウンロードの進捗状況は、該当するNSURLオブジェクトの、NSURLUbiquitousItemDownloadingStatusKeyキーの値で確認できます。この値は、ファイルが既にデバイス側に置かれているか、ダウンロード処理の途中であるか、を表します。より詳しい状況を、NSURLオブジェクトの他の属性を介して調べることも可能です。該当するキーについては、『NSURL Class Reference』を参照してください。

iCloudドキュメントに関するアプリケーション側の役割

ドキュメントに変更を施した旨の通知が、iCloudからいつ届くか分からないので、アプリケーションはそれに備えておく必要があります。OS XのNSDocumentクラスには必要な機能の大部分が組み込まれていますが、iOSの場合はアプリケーション側に実装しなければなりません。iOS/OS Xアプリケーションの実装に当たっては、iCloud側の変更に適切に対処できるよう、次のような作業が必要です。

iCloudを意識してドキュメントファイルの形式を設計する

ドキュメント形式の設計は、ネットワークを介した伝送性能にも影響を及ぼします。最も重要な選択肢は、ファイルパッケージを使うか否か、という点です。

iOS版とMac版の両方を開発する場合は、互換性のあるドキュメントファイル形式にしてください。

ネットワーク伝送効率を考慮した設計

ドキュメントデータ形式が、独立したいくつかの部分に分かれるのであれば、ファイル形式としてファイルパッケージを使うとよいでしょう。ファイルパッケージは、NSFileWrapperオブジェクトを介してアクセスし、ドキュメントの各要素を個別のファイルやフォルダとして読み書きできますが、ユーザにとっては単一のファイルのように見えます。iCloudのアップロード/ダウンロードに際しては、ファイルパッケージの中身を個別に扱い、変更があった部分だけを転送します。

Xcode上で、ドキュメントファイル形式と対応する拡張子を、アプリケーションの「Info.plist」というプロパティリストファイルに登録します。具体的には、「CFBundleDocumentTypes」というキーに、アプリケーションが認識し、開くことができるファイル形式を指定します。ファイルの内容に応じて、ファイル拡張子とUTI(統一型識別子、Uniform Type Identifier)を指定してください。システムはこの情報に基づいて、ファイルパッケージとアプリケーションを対応づけ、OS Xの場合はファイルパッケージを通常ファイルのように表示します。

ドキュメントの状態を永続化するための設計

ドキュメントベースのアプリケーションの多くは、ドキュメント単位で状態を管理します。たとえばベクトル図形描画アプリケーションの場合、最後に使った描画ツールや、直近に選択した描画要素を、ドキュメントごとに記憶しておくと便利でしょう。

ドキュメント単位の状態情報は、次の2箇所に保存できます。

  • ドキュメントのファイルパッケージ(またはファイル)内。この方式の場合、たとえば電子メールでドキュメントを送信すれば、状態情報もそのまま残ります。

  • ファイルパッケージ(またはファイル)外に置いてドキュメントと関連付ける。この方式は、状態情報を開示(共有)したくない場合に向いています。ただし、ドキュメントオブジェクトの管理対象外になるので、組み込みの食い違い解消機能は使えません。アプリケーション側の責任になります。

編集機能を備えたアプリケーションの場合、どちらの方式であっても、ドキュメント本体を編集しない限り、状態を保存しないよう注意してください。この処理が適切でないと、つまらない食い違いが生じて、ネットワーク帯域と電力を無駄に消費することになってしまいます。

たとえば、iPad上で長大なテキストドキュメントを編集しており、現在は1ページ目を対象に作業しているとします。その後、iPhone上で同じドキュメントを開き、最終ページまでスクロールしました。ここで行儀の悪いアプリケーションは、何の変更も施していないのに、ドキュメントの末尾に当たるスクロール位置を保存してしまうかもしれません。その後、iPadに戻ってドキュメントを開き、編集を再開しようとすると、スクロール位置のデータに食い違いが生じてしまいます。システムはその旨の印(UIDocumentStateInConflict)をつけ、新しい版(食い違いの解消により自動的に選ばれた方)に従って最終ページまでスクロールします。一方、行儀のよいアプリケーションは、iPhone上でスクロール位置が変わっても、中身は変わっていないので保存しません。

アプリケーションのさまざまな使い方を想定し、ユーザにとってどうすれば使い勝手がよいかを考慮して設計してください。次のような状態情報に注意が必要です。

  • ドキュメントのスクロール位置

  • 要素の選択状態

  • 最後に開いたときのタイムスタンプ

  • 表の整列順序

  • ウインドウの大きさ(OS Xの場合)

こういった状態情報を保存するのは、ユーザが保存に値するような変更を施した場合に限る、というのが基本的な考え方です。OS Xの場合、組み込みの「Resume」機能を使えば、ドキュメントベースのアプリケーションの多くに必要な状態情報を保存できます。これ以外にも制御が必要であれば、更新回数を更新する際、更新の種類としてNSChangeDiscardableを指定するとよいでしょう。

頑健性とプラットフォーム間の互換性を考慮した設計

iCloudを考慮してドキュメントファイル形式を設計する際には、次の点を頭に入れておいてください。

  • プラットフォームに依存しないデータ表現を用いる。プレフィックスが「UI」(iOS)または「NS」(OS X)となっているクラスは、同じような機能であっても、直接的な互換性はありません。たとえば、色(UIColorNSColor)、画像(UIImageNSImage)、ベジエパス(UIBezierPathNSBezierPath)など、あらゆるクラスについて言えることです。

    たとえばOS XのNSColorオブジェクトは、色空間(NSColorSpace)に基づいて定義されていますが、iOSには色空間クラスがありません。

    このようなクラスのデータを保存するドキュメントファイル形式を設計する際には、どちらのプラットフォームでもネイティブ表現を忠実に再構築できるような、中間表現を工夫してください(図3-6を参照)。NSDocumentオブジェクトは、データをディスクに保存する(手順1)際、プラットフォーム固有のデータ型を、プラットフォームに依存しない中間表現に変換します。このデータをiCloudに保存し、さらにユーザの他のデバイスにダウンロードします(手順2および3)。iOS版のアプリケーションはデータを抽出し(手順4)、中間表現からiOS固有のデータ型に変換します。

    図3-6 特定のプラットフォームに依存しないデータ表現

    逆にiOSアプリケーションが保存する際も同様に、後でどちらのプラットフォームでも表示できるよう、プラットフォーム非依存の表現にして保存します(図3-6を参照)。

    ドキュメント形式に組み込まれているプラットフォーム固有のデータ型それぞれについて、中間表現としてプラットフォーム間で共有できるような、適当な低レベルデータ型がないか調べるとよいでしょう。たとえば色クラス(UIColorNSColor)には、Core ImageのCIColorインスタンスから色オブジェクトを生成する初期化メソッドがあります。

    iCloudに保存するデータを用意する際には中間表現変換し、iCloudからファイルを読み込む際には中間表現から変換します。NSCoderまたはその具象サブクラスの適当なメソッドで、ドキュメントのオブジェクトグラフをエンコード/デコードすると、この変換を実行したことになります。

  • プラットフォームごとの座標系を考慮する。既定の画面座標系は、iOSとOS Xで異なるため、描画方法やビューの位置決め方法に違いが生じます。座標情報を中間表現に変換する際には、この違いを考慮しなければなりません。詳しくは、『Drawing and Printing Guide for iOS』の「Coordinate Systems and Drawing in iOS」を参照してください。

  • ファイル名は大文字と小文字の区別がないものとして扱う。OS Xのファイルシステムは、特殊な例外を除き、大文字と小文字を区別しません。たとえばmydoc.txtMyDoc.TXTというファイルを、同じディレクトリに置くことはできません。一方、iOSはそれらのファイル名を異なるものとして扱います。

    ドキュメントファイルは、どのプラットフォームでも読み書きできるよう、大文字と小文字の区別はないものとして扱ってください。

  • ファイル形式のバージョン番号をつける。将来ドキュメント形式を変更するかもしれないので、バージョン番号のつけ方を設計に組み込んでおき、ドキュメント形式のプロパティとしてバージョン番号を与えてください。

    ドキュメント形式にバージョン番号をつけること自体も優れた手法ですが、iCloudを意識してプラットフォーム非依存の形式にする場合はなおさら重要になります。iCloudユーザは、さまざまなデバイスでドキュメントを表示しますが、各デバイスのアプリケーションを一斉に更新しないかもしれません。その結果、Mac上には新版のアプリケーションがあるけれども、iPad上では古いiOSアプリケーションが動いている、ということが起こりえます。

    あるいは、旧版はiOS用のみで、その後OS X用に新版を配布することになった場合も、ドキュメント形式を変更する必要が生じるかもしれません。各ドキュメントにバージョン番号を埋め込んでおけば、それに応じて適切に対処できるでしょう。たとえば、iOSのみの旧形式ドキュメントを、OS Xでは読み込み専用にすることも考えられます。関与するデバイスやプラットフォーム、アプリケーションがそれぞれ異なるような、さまざまな状況を想定して検討してください。できる限り、iCloudベースのドキュメントを使っているユーザに不都合を与えることのないようにしなければなりません。

ファイル形式の設計技法について詳しくは、「ドキュメントデータのタイプ、形式、戦略の選択」(『iOS用のドキュメントベースアプリケーション:プログラミングガイド』)、または「Handling a Shared Data Model in OS X and iOS」(『Document-Based App Programming Guide for Mac』)を参照してください。

ドキュメントベースのワークフロー

表3-1に、ドキュメントベースのアプリケーションの、典型的なワークフローをいくつか示します。ワークフローごとに、典型的に用いる主なクラスと、そのタスクの簡単な説明を載せておきます。

表3-1 ドキュメントベースのアプリケーションの典型的なワークフロー

ワークフロー

実装

説明

標準的なドキュメントを新規作成する

UIDocument(iOS)

NSDocument(OS X)

ドキュメントオブジェクトを使って、ドキュメント形式のデータ構造を生成、管理します。ドキュメントクラスには、新規ドキュメントをiCloudコンテナやローカルストレージに保存する機能が組み込まれています。

Core Dataドキュメントを新規作成する

UIManagedDocument(iOS)

NSPersistentDocument(OS X)

Core Dataドキュメント用のサブクラスを使って、Core Dataストアを作成、管理します。詳しくは、「iCloudに対応したCore Dataアプリケーションの設計」を参照してください。

iCloudドキュメントのURLを取得する

NSMetadataQuery(iOS)

自動(OS X)

iOSの場合、メタデータのクエリオブジェクトを使って、iCloudドキュメントの更新情報を検索、取得します。

OS X v10.8以降では、ドキュメントの「Open」ダイアログに、メタデータの検索機能が組み込まれています。

ドキュメントを開くための選択画面を表示する

カスタムUI(iOS)

自動:ドキュメントアーキテクチャの一部として(OS X)

iOSの場合、ユーザドキュメントの選択画面を表示するのはアプリケーション側の責任です。アプリケーション全体の設計と整合するよう、簡潔で紛れのない形で実装しなければなりません。

OS X v10.8以降では、「Open」コマンドで開くファイル選択ダイアログが、はじめからiCloudに対応しています。

版の食い違いに対処する

UIDocumentNSFileVersion(iOS)

自動(OS X)

iOSの場合、ドキュメント自身が食い違いを検出、通知します。必要に応じてNSFileVersionオブジェクト(ドキュメントのレビジョンごとにインスタンスを生成)を使ってください。

iCloudベースのドキュメントを移動/複製/削除する

NSFileCoordinator

NSFileManager

ディスク上のファイルは、NSFileManagerクラスを使い、ファイルコーディネータオブジェクトのコンテキスト内で操作してください。

上記の具体的な実装方法については、プラットフォームに応じたプログラミングガイドを参照してください。iOSの場合は『iOS用のドキュメントベースアプリケーション:プログラミングガイド』、Macの場合は『Document-Based App Programming Guide for Mac』です。

iCloud Driveでドキュメントストレージを有効にする

ドキュメントをiCloud Driveに保存するには、「Capabilities」ペインでiCloudを有効にし、NSUbiquitousContainersキーをInfo.plistファイルに追加します。

アプリケーションのコンテナを指定するには、NSUbiquitousContainersキーにアプリケーションのコンテナIDを設定します。さらに、その他のNSUbiquitous…キーを使って各コンテナの共有形態を指定します。

<key>NSUbiquitousContainers</key>
    <dict>
        <key>iCloud.com.example.MyApp</key>
        <dict>
            <key>NSUbiquitousContainerIsDocumentScopePublic</key>
            <true/>
            <key>NSUbiquitousContainerSupportedFolderLevels</key>
            <string>Any</string>
            <key>NSUbiquitousContainerName</key>
            <string>MyApp</string>
        </dict>
    </dict>

このように設定すると、iCloud Driveを介して、アプリケーションのiCloudコンテナに格納されたファイルにパブリックアクセスできるようになります。iCloud Driveでは、アプリケーションのドキュメントを保存するためのアプリケーション用フォルダがユーザのiCloud Driveフォルダに作成されます。NSUbiquitous…の各キーについて詳しくは、『Information Property List Key Reference』の「Cocoa Keys」を参照してください。