2010-12-14

オフライン機能の実現 #2 (MacBloggerGlass)

GData オブジェクトの保存については実現の目処が立った(→「GData オブジェクトを保存する」)ので、今回はオフライン機能全般について検討してみた。

ユーザ体験

前にも書いたように、MacBloggerGlass はドキュメントアーキテクチャに基づいたアプリではない。言わば(Blogger で作ったブログ専用の)ブログブラウザだが、アプリの分類では特殊用途の RSS リーダだ。そして、ユーザ体験から言えば Mail.app と言ったところ(ただし閲覧専用)。

Mail.app (を始めとするメーラのほとんど)がそうであるように、このアプリの場合もユーザが明示的に保存を指示することはない。Mac (や PC) のアプリでは、こういうタイプは少数派だが、iOS アプリではむしろ主流となっている。データを保存し読み込むというよりは、(アプリの終了によって)中断した状態を復元する方式だ。

現状の UI は、Google アカウントを切り替えることを想定したものではないが(わたし自身、複数のアカウントは使っていないし)、オフライン機能のためのデータ保存はアカウントごとに行う。これは、アカウントを切り替えたとしてもデータを上書きしないことを意味する。この方式を取った場合、アカウントごとにデータを削除する手段も用意すべきだが、それは将来の検討課題としておく。

一方で、ブログを切り替える UI を持つのだから、データの保存をブログごとに独立して行うのは当然。ただし、こちらもデータの削除は将来の検討課題に。

データの保存はユーザに意識させないが、データの更新はユーザに明示的な指示を出してもらう。メニューの「Reload」をそれに使う。アプリの動きとしては、(User Defaults に保管されている)ブログ ID に対応するフィードデータが存在した場合、データの取得は行わなず、保存されたデータから GData オブジェクトを復元し、表示する。もちろん、データが保存されていないければ、特にユーザからの指示がなくとも取得する。

デザイン(設計)

保存先は ~/Library/BloggerGlass とする。この中にアカウント名でフォルダを作る。そこにさらに Blog ID でフォルダを作り、データファイルはこの Blog ID によるフォルダに置く。ファイル名は feedblogpost.xml とする。将来的にフィードデータを分割保存することがあるかもしれない。その場合は feedblogpost-20101214.xml のように日付をファイル名に取り込む。

また、アカウントごとのブログ一覧のフィードも、アカウント名のフォルダ直下に置く。ファイル名は feedblog.xml とする。

データファイルの形式は XML のテキスト。言わゆる human readable な形にしておく。

「ユーザ体験」のところで書いたように、ユーザに保存を指示させない方式だとすると、そのトリガーをどうするか? まずは、フィードの取得が完了し(GData オブジェクトが作られ)た直後に実行される、GData ライブラリのコールバックが良いだろう。ユーザへのレスポンスの速さが気になるなら別スレッドで非同期に保存すれば良い。ただ、今の段階では同期的に保存する方式で。

では読み込みのトリガーは? これはブログの切り替えになる。つまり、環境設定パネルでブログが切り替えられた通知のハンドラで行うことになる。今は、このタイミングでフィードをネットワーク経由で取得に行っている。保存が実現でき後は、ここでまず保存されたデータの有無を確認し、あればそれを読み込み、なければネットワークにアクセスする、となる。

データの保存、読み込みはアプリコントローラとは独立させる。FeedManager クラス(仮名)を作り、ローカルのデータとネットワーク経由の取得の違いを隠す。アプリコントローラは FeedManager にアカウント名と Blog ID をわたして、FeedBlogPost オブジェクトを受け取ることになる。

実装に関する考慮点

~/Library に保存用のフォルダを作る際には、パスを直接指定するのではなく(そもそもユーザ ID を知らずにユーザのホームディレクトリを直接指定できない)、Cocoa に用意された API 経由でライブラリ用フォルダを取得する。それが NSSearchPathForDirectoriesInDomains 関数だ。

NSSearchPathForDirectoriesInDomains 関数は 3 つの引数を取る。1 つ目がフォルダの種類、2 つ目がフォルダのドメイン(のマスク)、そして 3 つ目がチルダ(~)を展開するか否か。

ドメインマスクには NSUserDomainMask、NSLocalDomainMask、NSNetworkDomainMask、NSSystemDomainMask、NSAllDomainMask のうちから 1 つを指定する。今回の実装ではユーザのライブラリフォルダ(のパス)を知りたいのだから、NSUserDomainMask で良い。

戻り値は配列になる。ユーザドメイン(のマスク)を指定すれば要素は 1 つだけだ。

以下に簡単なサンプルプログラムを示す。

int main (int argc, const char *argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    
    NSArray *paths =
    NSSearchPathForDirectoriesInDomains(NSLibraryDirectory,
                                        NSUserDomainMask,
                                        YES);
    
    NSString *path;
    for (path in paths) {
        NSLog(@"Path: %@", path);
    }
    
    [pool drain];
    return 0;
}

上記のプログラムの実行結果は以下の通り(コンソールへの出力)。

2010-12-14 22:35:48.525 ExpApp[11456:903] Path: /Users/mnbi/Library

関連リンク

関連記事

0 件のコメント:

コメントを投稿