📁

"Using FileWrappers as File Containers"を日本語訳してみた

25 min read

iOS、macOSなどのドキュメントベースのアプリで使われるAPIにFileWrapperがあります。Appleのドキュメント"File System Programming Guide"の1つの章"Using FileWrappers as File Containers"でその仕様は詳しく解説されていますが、メチャクチャ長い英文なので読む気が失せます。なのでそのドキュメントを日本語に翻訳してみました。コードはObjective-Cですが、Swift読めるなら大体の雰囲気は掴めます。下記のリンクから原文が読めます。

https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileWrappers/FileWrappers.html#//apple_ref/doc/uid/TP40010672-CH13-SW2
内容が誤っている箇所、または訳の改善点などあれば、コメント欄で教えていただけると助かります。
本文↓

FileWrapperをファイルコンテナとして使う

テキスト、バイナリ、メタデータなどで構成された複雑なドキュメントは、そのデータを複数のファイルやディレクトリに保持するためにしばしば、Document Packagesで説明されているパッケージ形式をとります。NSFileWrapperクラスのインスタンス、またはファイルラッパーを使うことで、アプリで効率的にパッケージを管理できます。

ファイルラッパーは、隠れたファイルシステムのオブジェクトを反映した構造とリンクした、パッケージの階層構造内のノード(ファイル、シンボリックリンク、ディレクトリ)を表現します。ノードそのもののように、ファイルラッパーには3つの種類があります。

  • 標準ファイルラッパーは標準ファイルの内容と属性へのアクセスを提供します。標準ファイルの例としては、画像(特定のフォーマットで単一の画像を表現するバイナリを含んでいる)、plistファイル(メタデータを構造化されたテキストとして保持している)、そして単純なテキストファイル(任意の連続したテキストブロックを含んでいる)などがあります。

  • シンボリックリンクファイルラッパーはファイルシステム上のシンボリックリンクを表現します。内容を直接保持するの代わりに、これはドキュメントパッケージ内の他の場所にある、標準ファイル、ディレクトリ、または他のシンボリックリンクへの参照を提供します。

  • ディレクトリファイルラッパーは、対応するディレクトリの内容を表現する、他の(任意の種類の)ファイルラッパーの集合を保持します。

これらのファイルラッパーは下記の利点を伴って、ドキュメントパッケージの内容(と属性)を表現し、操作することを可能にします。

  • 単一の操作点。アプリは最上位のファイルラッパーへの参照だけを保ちます。ドキュメントないのディレクトリやファイルの隠れた階層は、あなたがアクセせするリンクされたファイルラッパーの木へ、最上位のファイルラッパーからエンコードされます。

  • 効率的なディスクアクセス。(繰り返される)保存処理の中で、システムは、明示的に変更されたファイルラッパーのみをディスクに書き込みます。同時には変更されない多数のデータがある場合、この機能はディスクアクセスを減らすのに役立ちます。これはiCloudベースのドキュメントでは、不必要なネットワークアクティビティを避けるために特に重要です。

Cocoaドキュメントでファイルラッパーを使う

Cocoaドキュメントアーキテクチャは、ファイルラッパーとシームレスに統合された多くの機能を自動的に提供しているため(Document-Based App Programming Guide for Macに記載)、ファイルラッパーをCocoaドキュメント内で使用することが多いです。この場合、ファイルラッパーを使用するのは通常3つの場面です。

  • ディスクからドキュメントを読み取る場面。ドキュメントがディスクから読み取られる時、アプリのNSDocumentクラスのreadFromFileWrapper:ofType:error:メソッドのオーバライドは、第1引数にドキュメントパッケージを表すファイルラッパーを受け取ります。この最上位の開始点より、ドキュメントパッケージに含まれるファイルラッパーの階層を横断し、ドキュメントの内容を抽出したり、データモデルにデータを入れることができます。また、後に使用するため、最上位のファイルラッパーへの参照を保持します。

  • ドキュメントモデルを更新する場面 。データモデルが変化したときはいつでも、removeFileWrapper(_:)メソッドを使用して、影響を受ける単一または複数のファイルラッパー(ただし影響を受けたものだけ)を階層から削除します。これは、影響を受けるファイルラッパーは次の保存処理の際に再生成される必要があることを示唆します。

  • ドキュメントをディスクに書き込む際。ドキュメントがディスクに書き込まれる時、読み取りの処理が逆になります。アプリの、NSDocumentクラスのfileWrapperOfType:error: メソッドのオーバーライドは、システムにドキュメントパッケージの最上位のエンティティである単一のファイルラッパーを提供します。しかし、まず、欠如している「サブ」ファイルラッパーを生成します。新規のドキュメントの場合、それは全部になるかもしれません。以前にディスクから読み取られ、readFromFileWrapper:ofType:error:によって提供されたラッパーが保持されているドキュメントについては、モデルの変更によって削除されたものだけです。

ドキュメントサブクラスでドキュメントパッケージを管理する

Cocoaドキュメントでドキュメントパッケージを管理するためにファイルラッパーを使う方法の具体的な例として、ユーザーが表示、非表示を選択できるテキストと画像からなる単純なドキュメントを考えましょう。このような異質なデータを1つのファイルにまとめるのではなく、ドキュメントパッケージとして定義します。この単純な例では3つの標準ファイルを含むディレクトリです。

  • Text.txtという、テキストを格納するテキストファイル
  • Image.pngという、画像を保持する画像ファイル
  • MetaData.plistという、画像がUIに表示されるか否かのBool値を保持するplistファイル

NSDocumentのサブクラスの実装を始めるにあたり、まずパッケージ内のファイル名とメタデータの辞書キーを定義します。

// ファイル名
static NSString* const ImageFileName    = @"Image.png";
static NSString* const TextFileName     = @"Text.txt";
static NSString* const MetaDataFileName = @"MetaData.plist";
 
// メタデータキー
static NSString* const MetaDataHiddenKey = @"HiddenKey";

次に、ドキュメント内にプロパティのグループとして単純なデータモデルを作り、最上位のドキュメントオブジェクトへの参照を保持します。

@interface MyDocument : NSDocument
 
// モデル
@property (copy) NSString* text;
@property (strong) NSImage* image;
@property (strong) NSMutableDictionary* metaDataDict;
 
// 最上位ファイルラッパー
@property (strong) NSFileWrapper* docWrapper;
 
@end

ドキュメントを開くときにファイルラッパーの階層を横断する

ディスクからドキュメントを読み取る際、システムはドキュメントのreadFromFileWrapper:ofType:error:メソッドを呼び出し、第1引数にドキュメントパッケージを表すファイルラッパーを渡します。このメソッドを、最上位のファイルラッパーにファイルラッパーのリストを求めることから始めましょう。

- (BOOL)readFromFileWrapper:(NSFileWrapper *)fileWrapper
                     ofType:(NSString *)typeName
                      error:(NSError **)outError
{
    NSDictionary *fileWrappers = [fileWrapper fileWrappers];

fileWrappersプロパティは、ディレクトリのファイルラッパーの時のみ有効で、パッケージの最上位ディレクトリ内のファイル(またはディレクトリ)に対応するファイルラッパーの辞書を内包しています。

この単純な例では、パッケージはちょうど3つのファイルをフラットなディレクトリ構造の中に含みます。もっと複雑なドキュメントでは、任意の深さの木構造のディレクトリ、ファイル、シンボリックリンクを定義し、それぞれの深さのレベルにてディレクトリのfileWrappersプロパティを再帰的に呼び出すことで、その木構造の中を横断します。

静的なパッケージ構造を定義する時、そして読み書きするのはあなたのコードだけである時、それぞれのファイルラッパーのタイプを知っていて、それを適切に扱うことができます。しかし場合によっては、前もってファイルラッパーのタイプを知らないかもしれません。例えば、ドキュメントフォーマットがファイルやディレクトリの動的な木構造を許容し、それをパッケージを読み取る間に知る場合、regularFiledirectorysymbolicLinkメソッドを使うことで、それぞれの遭遇したファイルラッパーにそのタイプを問い合わせることができます。

次に、階層内の任意のレベルにあるファイルラッパーのコレクションから、想定するファイルラッパー要素を探します。例を続けると、まず画像ファイルラッパーを、その名前を辞書キーとして使うことで取得します。もし結果がnilであるなら、パッケージには画像ファイルはありません(もしかするとドキュメントの最後の保存の際に画像が除外されたからかもしれません)。この場合、画像がないことを示すためにドキュメントの画像プロパティはnilのままになります。もしラッ パーがnilでなければ、ファイルラッパーによって指し示された画像ファイルからimageプロパティにデータを読み込みます。

    // 画像データを取得する
    NSFileWrapper *imageWrapper = fileWrappers[ImageFileName];
 
    if (imageWrapper != nil) {
        NSData *imageData = [imageWrapper regularFileContents];
        self.image = [[NSImage alloc] initWithData:imageData];
    }

画像ファイルラッパーは標準ファイルラッパーであり、NSDataオブジェクトとしてファイルの内容を返すregularFileContentsメソッドを提供します。このデータは画像を表現していることを、ドキュメントをそう定義したため知っています。なので、データをNSImageオブジェクトを初期化するために使います。

同様に、テキストファイルラッパーをモデルオブジェクトに読み込みます。この要素に対しては、ラッパーが見つからなかった時にはデフォルトの空の文字列を提供します。

 NSFileWrapper *textWrapper = fileWrappers[TextFileName];
 
    if (textWrapper == nil) {
        self.text = @“”;   // デフォルトでからのテキスト
    } else {
        NSData *textData = [textWrapper regularFileContents];
        self.text = [[NSString alloc] initWithData:textData
                                          encoding:NSUTF8StringEncoding];
    }

最後に、メタデータを読み込みます。この場合、ファイルラッパーが見つからなかった時はドキュメントパッケージが不正であることを表すので、NSErrorのインスタンスを生成します。そして読み取りメソッドのoutErrorポインタが提供されている場合はそれにアドレスを格納します。

  NSFileWrapper *metaDataWrapper = fileWrappers[MetaDataFileName];
 
    if (metaDataWrapper == nil) {
        if (outError) {
            *outError = <# Corrupt Package Error #>;
        }
        return NO;      // 読み込み失敗
    } else {
        NSData *metaData = [metaDataWrapper regularFileContents];
        self.metaDataDict = [NSPropertyListSerialization
                              propertyListWithData:metaData
                                           options:NSPropertyListImmutable
                                            format:NULL
                                             error:outError];
        if (self.metaDataDict == nil) {
            return NO;  // 読み込み失敗
        }
    }

読み取り処理の最後に、ドキュメント内の最上位のファイルラッパーへの参照を保持し(後に、モデルを変更したりドキュメントを保存したりするときに使用するため)、YESを返すことで成功を報告します。

    self.docWrapper = fileWrapper;
    return YES;  // 読み込み成功
 
} // readFromFileWrapperの終わり

メモ: パッケージの1部分が欠けていたり読めなかったりする時、無視して何もしない、無視してデフォルト値を設定する、もしくは上記の例のように読み込みエラーを示す、などをすることができます。どれを選ぶかはアプリの要件によります。

モデルが変更したときにファイルラッパーを無効にする

ユーザーがドキュメントに変更を加えたときはいつでも、階層内の1つまたは複数のファイルラッパーは無効になることがあります。上記の例を続けると、このサンプルアプリではユーザーは画像を追加、交換、削除できるので、ドキュメントのサブクラスは画像(nilの場合もある)を設定するためのメソッドを持ちます。モデルを更新した後、このメソッドは付随的に画像ファイルラッパーを探します。ファイルラッパーが存在する場合、そのメソッドはNSFileWrapperクラスのremoveFileWrapperを使ってドキュメントからそれを取り除くことで、それを無効にします。

- (void)updateImageModel:(NSImage *)image
{
    // モデルを更新
    self.image = image;
 
    // 画像ファイルラッパーが存在する場合、無効化する
    NSFileWrapper *imageWrapper = self.docWrapper.fileWrappers[ImageFileName];
    if (imageWrapper != nil) {
        [self.docWrapper removeFileWrapper:imageWrapper];
    }
}

ディスク上の対応するファイルはもう有効ではないので、そのファイルラッパーを階層上から破棄します。もう少し先にあるように、次の保存時に、ドキュメントは古いものを上書きするために、最新のモデルデータを使って新しいファイルラッパー(と対応するファイル)を生成します。

同様に、テキストとメタデータのモデルの更新メソッドを提供します。

- (void)updateTextModel:(NSString *)text
{
    // モデルを更新
    self.text = text;
 
    // テキストファイルラッパーが存在する場合、無効化する
    NSFileWrapper *textWrapper = self.docWrapper.fileWrappers[TextFileName];
    if (textWrapper != nil) {
       [self.docWrapper removeFileWrapper:textWrapper];
    }
}
 
- (void)updateHidden:(BOOL)hidden
{
    // モデルを更新する
    [self.metaDataDict setValue:@(hidden) forKey:MetaDataHiddenKey];
 
    // メタデータファイルラッパーが存在する場合、無効化する
    NSFileWrapper *metaWrapper = self.docWrapper.fileWrappers[MetaDataFileName];
    if (metaWrapper != nil) {
        [self.docWrapper removeFileWrapper:metaWrapper];
    }
}

与えられたモデルの更新は、関係するファイルラッパーにのみ影響すること気づくでしょう。これはディスクアクセスを最小化するのに役立ちます。例えば、典型的なこのドキュメントタイプの使用シナリオはおそらく、ドキュメントを開いた後、ユーザーが何度もテキストを更新し、たまに画像を更新する、というものでしょう。結果として、テキストファイルラッパーが除かれていても、画像ファイルラッパー(とその根底のファイル)は元のままで、何回もの保存処理に渡って変更されません。これによってディスク上のデータを何回も更新する必要がなくなります。

保存時に、欠けたファイルを追加する

ユーザーがドキュメントを保存するとき、または自動保存中に、システムはドキュメントのfileWrapperOfType:error:メソッドを呼び出し、ドキュメントのファイルラッパーを探してディスクに書き込みます。まずはファイルラッパーがドキュメントのプロパティに既に存在するか確認することから始めましょう。もしないなら、生成します。

- (NSFileWrapper *)fileWrapperOfType:(NSString *)typeName error:(NSError **)outError
{
    NSError* error = nil; // 後に必要となった時に設定する
 
    if (self.docWrapper == nil) {
        self.docWrapper = [[NSFileWrapper alloc]
                                 initDirectoryWithFileWrappers:@{}];
    }

ドキュメントラッパーを初期化するとき、そのタイプ(標準、ディレクトリまたはシンボリックリンク)を指定します。この場合、ラッパーはディレクトリであるドキュメントパッケージを表すので、initDirectoryWithFileWrappers:初期化メソッドを使います。このディレクトリが含むファイルラッパーのコレクションは始めは空で、すぐに埋められます。次に、ドキュメントラッパーのfileWrappersプロパティを確認することで、すでにドキュメントファイルラッパーに含まれているファイルラッパーを取得します。

  NSDictionary *fileWrappers = self.docWrapper.fileWrappers;

ドキュメントラッパーを生成したばかりなら、それはもちろん空ですが、開く処理の最後に保存した既存のドキュメントのラッパーの場合は、それ以降、または最後の保存処理の時から、モデルの更新で無効化されていない全てのファイルラッパーを含みます。

ドキュメント内にあると想定するけれど存在しないファイルラッパーそれぞれについては、モデルデータから生成します。画像ラッパーから始めましょう。それがコレクション内に存在しないけれど、モデル内に格納された画像がある場合、画像コンテンツを含むNSDataオブジェクトを生成します。そして、標準ファイルラッパーを生成するためのinitRegularFileWithContentsメソッドをデータとともに使ってファイルラッパーを生成します。ラッパーに名前をつけて、ドキュメントラッパーのコレクションへ追加します。

    if ((fileWrappers[ImageFileName] == nil) && (self.image != nil)) {
        // 画像データをPNGとして取得する
        NSData *imageData = [NSBitmapImageRep
                  representationOfImageRepsInArray:[self.image representations]
                                         usingType:NSPNGFileType
                                        properties:@{}];
 
        // 必要である場合、PNGに変換する
        if (imageData == nil) {
            imageData = [self.image TIFFRepresentation];
            NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc]
                                             initWithData:imageData];
 
            imageData = [imageRep representationUsingType:NSPNGFileType
                                               properties:@{}];
        }
 
        // ファイルラッパーを生成し、名付け、追加する
        NSFileWrapper *imageFileWrapper = [[NSFileWrapper alloc]
                                            initRegularFileWithContents:imageData];
        [imageFileWrapper setPreferredFilename:ImageFileName];
        [self.docWrapper addFileWrapper:imageFileWrapper];
    }

モデル内に画像が存在しない時、ドキュメントは画像ラッパーを生成せず、ファイルはパッケージから除かれることに気づくでしょう。先に説明したように、読み取り処理はこの状況を許容します。

では、テキストラッパーが欠けている場合、追加しましょう。この場合、1回の呼び出しで標準ファイルを生成、名付け、そして追加するためのaddRegularFileWithContents:preferredFilename:という便利なメソッドを利用しましょう。

    if (fileWrappers[TextFileName] == nil) {
        NSData *textData = [self.text dataUsingEncoding:NSUTF8StringEncoding];
        [self.docWrapper addRegularFileWithContents:textData
                                  preferredFilename:TextFileName];
    }

メタデータラッパーを追加します。繰り返しになりますが、欠如している場合にだけです。これは別の標準ファイルラッパーですが、この場合シリアライズされたプロパティリストのデータから作られます。

    if (fileWrappers[MetaDataFileName] == nil) {
        NSError *plistError = nil;
        NSData *propertyListData = [NSPropertyListSerialization
                                     dataWithPropertyList:self.metaDataDict
                                                   format:NSPropertyListXMLFormat_v1_0
                                                  options:0
                                                    error:&plistError];
 
        if (propertyListData == nil || plistError != nil) {
            error = <# an NSError #>;
        } else {
            [self.docWrapper addRegularFileWithContents:propertyListData
                                      preferredFilename:MetaDataFileName];
        }
 
    }

最後に、完全なドキュメントラッパーを返します。これはシステムによって、エラーが起きるまで再帰的にディスクに書き込まれます。エラーが起きた場合は、nilを返して、outErrorを下記のように設定します。

    if (error) {
        if (outError) {
            *outError = error;
        }
        return nil;
    } else {
        return self.docWrapper;
    }
 
} // fileWrapperOfTypeの終わり

直接ファイルラッパーを使う

ドキュメントベースのアプリでパッケージを開くとき、システムはOpen panelを使ってユーザーに適切なタイプのファイルシステムオブジェクトを入力するよう促し、それを使って最上位のファイルラッパーを初期化し、アプリに届けます。同様に、新しいパッケージを保存するとき、システムはSave panelを使ってファイルシステムの場所を入力するのを促し、ディスクにファイルラッパーを書き込みます。これらの機能(加えてファイル連携、undoサポート)を無料で使えます。

しかし場合によっては、Cocoaドキュメントの全ての機能は必要ではない時があります。ユーザーのドキュメントを個別に管理するよりは、アプリのApplication Supportディレクトリにパッケージを置いて、何らかのコレクションを表すアプリ全体のライブラリを保持することもあるでしょう。このようなアプリの例として、メールアプリや写真整理アプリなどがあるでしょう。このような場合でもファイルラッパーを使うことで恩恵を受けられます。少しやることが増えるだけです。

ファイルラッパーを読み込む

パッケージを直接開いた時は、ラッパーを作ってinitWithURL:options:errorメソッドを使って初期化します。このメソッドはファイルシステムで見つかったオブジェクトに基づいて、正しいタイプ(標準ファイル、シンボリックリンク、またはディレクトリ)を自動的に割り当てます。
この場合、アプリはファイルの場所を表すNSURLオブジェクトを提供します。どんな問題もすぐに探知して報告することを保証するために、NSFileWrapperReadingImmediateオプションを使いましょう。そうしなければ、読み取り処理は遅れて実行されるので、読み取りのエラーは書き込む時まで現れません。

    NSError *error;
    NSURL *fileURL = <# a URL #>;
    NSFileWrapper *docWrapper = [[NSFileWrapper alloc] initWithURL:fileURL
                                                           options:NSFileWrapperReadingImmediate
                                                             error:&error];
    if (docWrapper == nil) {
        // 読み込みエラーを処理する
    } else {
        // パッケージの要素を読み込む
    }

パッケージが自分のアプリのプライベートなものである場合、それが想定した構造を持つと信頼し、その場合ドキュメントベースの例のようにその要素をデータモデルに読み込みます。しかし、パッケージが信頼できないソースに由来するなら、directoryプロパティ(またはisDirectoryという便利なメソッド)を使って、実際に最上位のファイルラッパーがディレクトリであり、適切な構造を取ることを確かめます。下記のコードは、パッケージがちょうど1つのテキストファイルを持つ必要がある場合、疑いを持って読み込む方法を示しています。

    // (疑いを持って)パッケージの要素を読み込む
    if (![docWrapper isDirectory]) {
        // パッケージではないというエラーを処理する
 
    } else {
        NSDictionary* fileWrappers = [docWrapper fileWrappers];
 
        NSFileWrapper* textWrapper = fileWrappers[TextFileName];
        if (([fileWrappers count] != 1) || (textWrapper == nil) || ![textWrapper isRegularFile])
        {
            // 不正なパッケージのエラーを処理する
 
        } else {
            NSData *textData = [textWrapper regularFileContents];
            self.text = [[NSString alloc] initWithData:textData
                                              encoding:NSUTF8StringEncoding];
        }
    }

ファイルラッパーを書き込む

ディスクにファイルラッパーを直接書き込む時、writeToURL:options:originalContentsURL:error:メソッドを使います。このメソッドはディレクトリラッパーとその全てのサブラッパーを、ファイルシステムのurlパラメータで指定された場所に再帰的に書き込みます。ディスク上に新しいコピーを生成するには(例えば、Save Asの処理を実装するとき)、originalContentsURLパラメータをnilに設定します。また、成功したか否かの戻り値を確かめて、失敗した場合はエラーを処理します。

    BOOL success = [self.docWrapper writeToURL:url
                                       options:0
                           originalContentsURL:nil
                                         error:&error];
    if (!success) {
        // エラーを調査する
    }

ファイルパッケージを新しい内容で上書きし、「場所に保存」を実行する時、originalContentsURLurlと同じに設定し、NSFileWrapperWritingAtomicNSFileWrapperWritingWithNameUpdatingの両方のオプションを含めることで、処理を最適化します。

    BOOL success = [self.docWrapper writeToURL:url
                                       options:NSFileWrapperWritingAtomic |
                                               NSFileWrapperWritingWithNameUpdating
                           originalContentsURL:url
                                         error:&error];
    if (!success) {
        // エラーを調査する
    }

NSFileWrapperWritingAtomicを使うと、NSFileWrapperはドキュメントは全体が書き出されるか、あるいは全く書き出されないことを保証します。これは、新しいバージョンを一時的な場所に生成して、最後に1ステップでオリジナルを置き換えることによって行われます。

訳註: NSFileWrapperWritingAtomicは、書き込み中にエラーが起きた時、途中まで書き込まれたファイルがそのまま残ってしまうのを防ぐためのオプションであると考えられます。詳しくはApple Developerのドキュメントを参照してください。

originalContentsURLパラメータを指定した場合、一時的なコピーを生成する間、NSFileWrapperは、それぞれのドキュメントのサブアイテムである標準ファイルの属性を、originalContentsURLで与えられたディスク上の場所にある要素の属性と比較します。NSFileWrapperは必要であれば新しい内容を書き込みますが、可能であれば対応する元のコンテンツへのハードリンクをファイルシステム上に作ります。これによって、変更されていない要素が必要とする処理の量が減ります。
最後に、NSFileWrapperWritingWithNameUpdatingオプションは、サブアイテムのファイル名がドキュメント内で最新に保たれることを保証します。なので、その後の「場所に保存」の処理は確実に同じ最適化を行います。

ファイルラッパーを読み込んだ、あるいは保存した後、ファイルシステム上の表現が変更されたかを確認する必要がある時、matchesContentsURLメソッドを使います。その確認は、最後にファイルが読み書きされた時に保存されたファイル属性に基づきます。ファイルラッパーの変更日時またはアクセス権限がディスク上のファイルのものと異なっていたら、そのメソッドはNOを返します。その時は、readFromURL:options:errorを使ってディスク上のファイルを再度読み込む、またはアプリに適切な別の方法をとることができます。

ファイル同期

訳註: NSFileCoordinatorNSFilePresenterを的確に表す日本語が見つからなかったので、そのままカタカナに直して表記しました。Apple Developerのドキュメントによると、NSFileCoordinatorはファイルの読み書きを調整し、NSFilePresenterNSFileCoordinatorがシステム上でのファイルの変更を知らせるためのインターフェースである、ということのようです。詳しくはそちらのドキュメントをご覧ください。

ドキュメントベースのアプリは自動的にファイル連携を使い、アプリはそれを使うために特別なことは何もする必要はありません。アプリがドキュメントベースではなく、直接パッケージを読み書きし、それをApplication SupportCache、または一時的なディレクトリの中でのみ行う場合、ファイル同期は通常は必要ありません。

一方で、アプリがファイルラッパーを直接管理し、ファイルシステムのユーザーがアクセスできる範囲で行うなら、ファイルを使用します。これは下記のことを意味します。

  • ドキュメントラッパーを管理するオブジェクトはNSFilePresenterプロトコルに準拠し、それをファイルプレゼンターにします。プロトコルを実装することで、ファイルプレゼンターは自分が管理したいパッケージのURLを示し、パッケージの内容に対して他のエンティティが行った操作に関するメッセージに適切に応答します。

メモ: ファイルコーディネーターに対して最上位のファイルラッパーだけを指定します。それぞれのファイルやディレクトリを個別に管理する必要はありません。

  • ファイルプレゼンターを初期化した際、NSFileCoordinatorのクラスメソッドを使用して、それをシステムに記録します。

  • 自分が管理しているドキュメントを読む、書く、または移動させるつもりであることをシステムに知らせます。これらを、自分で生成したNSFileCoordinatorの力を借りて実行します。

  • 自分が生成したファイルプレゼンターをメモリから解放する直前に、もう一度NSFileCoordinatorのクラスメソッドを使用することで登録解除します。

ファイル同期の詳細については、ファイルコーディネーターとファイルプレゼンターの役割をご覧ください。

ファイルラッパーの識別子にアクセスする

この章を通しての例は、希望するファイル名(preferred file name)が、親ファイルラッパーのfileWrappersの辞書を調べるためのキー、そしてファイルシステム上のファイル名、の両方として使用できるということに依存しています。多くの場合、これで十分です。

しかし厳密には、ディレクトリファイルラッパーに保持されているファイルラッパーは3つの異なる識別子を持ちます。

  • 希望するファイル名(preferred filename)。ラッパーを生成するときにこの識別子を与えます。これはラッパーを一意に識別しませんが、他の識別子の元となります。preferredFilenameプロパティを通じてアクセス可能です。

  • 辞書キー(dictionary key)。ファイルラッパーをディレクトリファイルラッパーへ追加し、ディレクトリ内に同じ希望するファイル名のファイルラッパーがない場合、、システムは希望するファイル名を辞書キーとして使います。もし重複していれば、システムは希望するファイル名に一意な接頭辞をつけることでキーを生成します。ファイルラッパーは、それを含むディレクトリファイルラッパー毎に異なる辞書キーを持てることを覚えておいてください。ファイルラッパーの辞書キーを取得するためには、それを含むディレクトリファイルラッパーでkeyForFileWrapperメソッドを使用します。

  • ファイル名(filename)。この識別子は希望するファイル名に基づきますが、希望するファイル名や辞書キーと同じである必要はありません。ファイル名は、ファイルラッパーを含むディレクトリラッパーを保存した時に、特にファイルラッパーがいくつかの異なるディレクトリラッパーに追加された時には、いつでも変わる可能性があります。なので、必要になった時は毎回ファイル名をファイルラッパーのfilenameプロパティから取得し直すべきで、キャッシュするべきではありません。

ファイルラッパーを送信する

ファイルラッパーをディスク上に保存することに加えて、ペーストボードを使って他のプロセスへ送信することができます。これをするには、serializedRepresentationメソッドを使って、ファイルラッパーの内容をNSFileContentsPboardTypeフォーマットで内包するNSDataオブジェクトを取得します。

NSData *serializedWrapper = [fileWrapper serializedRepresentation];

データを受け取ってから、initWithSerializedRepresentationメソッドを使ってファイルラッパーを再構成します。

NSFileWrapper *fileWrapper = [[NSFileWrapper] alloc]
                   initWithSerializedRepresentation:serializedWrapper];

ファイルラッパーをドキュメントパッケージとして定義する

デフォルトで、システムはドキュメントパッケージを単一の実態として扱わず、その代わりに普通のディレクトリとして扱います。これを乗り越えるために、ドキュメントのための適切にフォーマットされたUTIをエクスポートします。これによってファイルラッパーは単一のパッケージのように扱われることを保証されます。詳しくは、バンドルプログラミングの手引きをご覧ください。

Discussion

ログインするとコメントできます