NSFilePresenterはFoundationプロトコルであり、オブジェクトがiOSおよびmacOSのファイルシステムにおけるファイルおよびディレクトリの変更に関する通知を受け取ることを可能にします。クラスはプロトコルメソッドを実装し、NSFileCoordinatorを通じて登録します。その後、システムは追跡対象ファイルに対する任意の操作時に自動的にこれらのメソッドを呼び出します。Apple Developer Documentation(2025)によると、NSFilePresenterは書き込み競合を防ぐためにマルチスレッドのドキュメントアクセスを伴うアプリケーションで使用されます。プロトコルはNSFileCoordinatorと組み合わせて使用する必要があります—これによってのみ安全なアクセス調整が保証されます。
重要なポイント
NSFilePresenterは、Appleオペレーティングシステムでファイルとディレクトリの変更を追跡するために設計されたFoundationプロトコルです。プロトコルは、オブザーバーオブジェクトがファイルシステムイベント通知を受け取るために実装する一連のメソッドを定義します。
プロトコルの主な目的は、マルチスレッドシナリオで安全なファイルアクセスを提供することです。iOSおよびmacOSでは、複数のプロセスとスレッドがNSFileCoordinatorを介して同じファイルに同時にアクセスでき、NSFilePresenterは各参加者が最新のデータ状態を受け取ることを保証します。
このプロトコルはiOS 5.0およびmacOS 10.7以降Foundationに含まれています。ドキュメント、データベース、および異なるソースから同時に変更される可能性のあるファイル(iCloud同期や共同編集など)を扱うアプリケーションで使用されます。
ドキュメントベースのアプリケーション — NSFilePresenterの主な使用領域。UIDocumentまたはNSDocumentを扱うアプリケーションは、NSFileCoordinatorを介して自動的にプレゼンターとして登録されます。これにより、複数のウィンドウやデバイスから同じファイルを編集する際の競合を適切に処理できます。
iCloud同期 — 2番目の主要シナリオ。1つのデバイスでファイルが変更されると、iCloudはそれをすべての接続デバイス間で同期します。NSFilePresenterはアプリケーションにこれらの変更を通知し、タイムリーなインターフェース更新を可能にします。
マルチスレッドエディタ — 3番目のシナリオ。バックグラウンドキューがユーザーの作業と同時にデータをロードおよび保存するアプリケーションでは、NSFilePresenterがファイルの書き込みと読み取り中の競合状態を防止します。
動作メカニズム NSFilePresenterは委任モデルに基づいています。オブジェクトはプロトコルメソッドを実装し、NSFileCoordinatorを介して登録し、追跡対象ファイルが変更されるたびに呼び出しを受け取ります。システム自体が変更の発生時期と呼び出すメソッドを決定します。
プロセスは、オブジェクトがNSFileCoordinatorのインスタンスを作成し、ファイルURLを渡してコーディネーターのメソッドを呼び出すことから始まります。コーディネーターは、このURLにプレゼンターが登録されているかどうかを確認します。登録されている場合、読み取りまたは書き込みアクセスをブロックし、プロトコルメソッドを介してプレゼンターに今後の変更を通知します。
操作が完了した後、コーディネーターはロックを解除し、最終通知を呼び出します。重要なのは、プレゼンターが実行フローを制御しないことです—イベントに反応するだけです。NSFileCoordinatorが調整に完全に責任を持ちます。
準備フェーズ — 操作を実行する前に、コーディネーターはaccommodatePresentedItemDeletionまたはaccommodatePresentedSubitemDeletionを呼び出します。プレゼンターは状況を処理するか、エラーを返して操作をキャンセルできます。このフェーズにより、アプリケーションはファイルが変更される前にファイルとの作業を適切に終了できます。
通知フェーズ — 操作が完了した後、コーディネーターはpresentedItemDidChangeまたはpresentedSubitemDidChangeを呼び出します。プレゼンターはファイルが変更されたシグナルを受信し、その内容を再読み取りできます。ファイルの移動については、新しい場所とともにpresentedItemDidMoveToURLが呼び出されます。
完了フェーズ — コーディネーターはすべてのロックを解除し、リソースを解放します。プレゼンターは更新されたデータで作業を続行できます。3つのフェーズすべてが単一のスレッドで同期的に実行されるため、プロトコルメソッドは長時間のI/O操作なしで迅速に実行する必要があります。
NSFilePresenterプロトコルには、いくつかの必須メソッドとオプションメソッドが含まれています。唯一の必須プロパティはpresentedItemURLで、追跡対象のファイルまたはディレクトリのURLを返します。このプロパティがないと、オブジェクトはプレゼンターとして登録できません。
presentedItemURL — 追跡対象ファイルへのパスを返すURL?型のプロパティ。オブジェクトが複数のファイルを追跡する場合、プロパティは主要アイテムのURLを返します。ディレクトリの場合は、ディレクトリのURLを返します。
presentedItemDidChange — 追跡対象ファイルの内容が変更された後に呼び出されます。このメソッドで、プレゼンターは内部状態を更新し、データをリロードします。このメソッドは何が変更されたかについての情報を受け取りません—変更の事実のみです。
accommodatePresentedItemDeletion — ファイル削除の前に呼び出されます。プレゼンターは現在の状態を保存したり、ファイル記述子を閉じたり、NSErrorを返して操作をキャンセルしたりできます。メソッドがエラーを返した場合、削除操作は実行されません。
presentedItemDidMoveToURL — ファイルの移動または名前変更後に呼び出されます。メソッドは新しいURLを受け取り、プレゼンターはファイル参照を更新する必要があります。このメソッドを実装しないと、プレゼンターは存在しない古いパスを指し続けます。
NSFileCoordinatorとNSFilePresenterは切っても切れないペアです。NSFileCoordinatorはファイルアクセスを管理し、プレゼンターメソッドを呼び出します。プレゼンターはファイルシステムと直接連携しません—すべての操作はコーディネーターを通じて行われ、変更のアトミック性を保証します。
コーディネーターはNSFileCoordinatorクラスのaddFilePresenterメソッドを介してプレゼンターを登録します。登録後、プレゼンターは通知の受信を開始します。削除はremoveFilePresenterを介して行われます。システムはプレゼンターへの弱参照を保持するため、オブジェクトは追跡期間中存続している必要があります。
Apple WWDC 2022によると、NSFileCoordinatorはカーネルレベルの調整メカニズムを使用しており、ロック中のレイテンシを最小限に抑えます。最新のiOSバージョンでは、コーディネーターはSandboxおよびアプリ拡張機能との連携に最適化されています。
Intention — 各読み取りまたは書き込み操作は調整ブロックでラップする必要があります:coordinateReadingItemAtURLによる読み取り、coordinateWritingItemAtURLによる書き込み。コーディネーターはブロック実行中、他の参加者に対して自動的にファイルをロックします。
バッチ調整 — 複数のファイルを伴う操作にはバッチ調整が使用されます。コーディネーターは指定されたすべてのファイルをアトミックにロックし、操作を実行してロックを解除します。これはドキュメントセットの移動やコピー時に非常に重要です。
NSFilePresenterプロトコルを実装し、ドキュメントファイルの変更を追跡するDocumentPresenterクラスを作成しましょう。クラスにはファイルへの参照、内部データ、および有効性フラグが含まれます。
import Foundation
class DocumentPresenter: NSObject, NSFilePresenter {
var presentedItemURL: URL? {
return self.fileURL
}
var presentedItemOperationQueue: OperationQueue {
return self.queue
}
private let fileURL: URL
private let queue = OperationQueue()
func presentedItemDidChange() {
self.reloadData()
}
func accommodatePresentedItemDeletion() throws {
try self.saveCurrentState()
}
private func reloadData() {
let coordinator = NSFileCoordinator(filePresenter: self)
var error: NSError?
coordinator.coordinate(readingItemAt: self.fileURL,
options: [],
error: &error)
{ readURL in
guard let data = try? Data(contentsOf: readURL)
else { return }
self.processData(data)
}
}
private func processData(_: Data) {
// ドキュメントデータ処理
}
}
クラスは、ファイル変更時にデータをリロードするためのpresentedItemDidChangeと、削除前に状態を保存するためのaccommodatePresentedItemDeletionを実装します。操作キューにより、すべての通知が順次処理されることが保証されます。
プレゼンターの登録は、ドキュメントを開く際にNSFileCoordinator.addFilePresenterを介して行われます。コーディネーターに正しい読み取りオプションを渡すことが重要です—非変更操作にはwithoutChanges、即時アクセスが必要なシナリオにはimmediatelyAvailable。
最初のよくある間違いは、presentedItemOperationQueueの実装不足です。キューを指定しないと、通知が任意のスレッドに到着し、データ競合を引き起こす可能性があります。通知を処理するには、常にシリアルOperationQueueを使用してください。
2番目の間違いは、プレゼンターメソッドでのブロッキングです。プロトコルメソッドはコーディネーターから同期的に呼び出されます。プレゼンターが長時間の操作(データベース書き込み、ネットワークリクエスト)を実行すると、他のすべての参加者に対してコーディネーターをブロックします。重い操作はバックグラウンドキューに移動してください。
3番目の間違いは、accommodatePresentedItemDeletionの無視です。プレゼンターがこのメソッドを実装せず、エラーを返さない場合、現在の状態を保存せずにファイルが削除される可能性があります。このメソッドでは、データがまだディスクに書き込まれていない場合は常に保存してください。
4番目の間違いは、循環調整です。プレゼンターが通知メソッド内で同じファイルに対して再度コーディネーターを呼び出すと、デッドロックが発生します。ハンドラー内で調整を開始する前に、isCoordinatedOperationフラグを確認してください。
| 間違い | 結果 | 解決策 |
|---|---|---|
| 操作キューなし | マルチスレッドでのデータ競合 | OperationQueueを指定 |
| メソッドでのブロッキング | コーディネーターのハング | バックグラウンドスレッドへ移動 |
| 削除の無視 | 削除時のデータ損失 | 保存を実装 |
| 循環調整 | アプリケーションのデッドロック | isCoordinatedOperationフラグ |
よくある質問
NSFileHandleはデータの読み書きのための低レベルインターフェースであり、他のプロセスからの変更通知メカニズムを提供しません。NSFilePresenterは調整レベルで機能します:ファイルが変更されるたびに、ソース(別のスレッド、プロセス、iCloud)に関係なく、システムからイベントを受信します。
はい。NSFilePresenterはNSFileCoordinatorなしでは意味がありません。プレゼンターはハンドラーメソッドを定義するだけであり、コーディネーターがロックを管理し、これらのメソッドを呼び出します。コーディネーターなしでNSFilePresenterを使用すると、通知は配信されません。
可能ですが、制限があります。presentedItemURLプロパティは1つのURLのみを返すため、複数のファイルを追跡するには、サブアイテム用の追加メソッドを持つNSFilePresenterプロトコルが使用されます。代替方法は、ファイルごとに個別のプレゼンターインスタンスを作成することです。
NSFilePresenterはiOSサンドボックスと完全に互換性があります。アプリケーションは自身のコンテナ内のファイルのみを追跡できます。他のアプリケーションのファイルにアクセスするには、App GroupsまたはSecurity-Scoped Bookmarksが使用されます。コーディネーターはサンドボックスの許可範囲内で動作します。
presentedItemDidChangeメソッド内でdebounceまたはthrottleを使用してください。0.3〜0.5秒の遅延でタイマーを作成し、新しい呼び出しのたびにリセットします。安定化後にデータのリロードを実行します。これにより、単一の変更バッチの複数回処理を防ぎます。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。