NSFileCoordinatorは、複数のスレッド、プロセス、または拡張機能が同時に動作する際に安全なファイルアクセスを保証するiOSおよびmacOSのFoundationクラスです。Apple Developer Documentation, 2024によると、NSFileCoordinatorはファイルの読み書き中の競合状態を防ぎ、あるプロセスがデータを変更している間に別のプロセスがそのデータを読み取らないことを保証します。コーディネーターはiCloud Drive、File Provider Extension、およびあらゆるマルチスレッドファイル操作で使用されます。
重要なポイント
NSFileCoordinatorは、AppleがiOS 5およびmacOS 10.7 Lionで導入した、オペレーティングシステムレベルでのファイルアクセス同期メカニズムです。従来のロック(NSLock、pthread_mutex)とは異なり、コーディネーターはファイルシステムレベルで動作し、同じアプリケーション内のスレッド間だけでなく、異なるプロセス間のアクセスも調整できます。
NSFileCoordinatorの必要性は、iOSのSandboxアーキテクチャから生じます。各プロセス(アプリケーション、拡張機能、システムサービス)は、独自のファイルアクセスを持つ分離された環境で動作します。複数のプロセスが同時に同じファイルの読み書きを試みると(例えばiCloud Driveの同期中)、コーディネーターがないと競合状態が発生します。プロセスAがファイルを読み取っている間に、プロセスBが既に部分的に上書きしている状態です。
WWDC 2023によると、AppleはUbiquityコンテナ(iCloud Drive)およびFile Provider Extensionでのすべてのファイル操作にNSFileCoordinatorを使用することを強く推奨しています。調整を無視することは、iOSアプリケーションにおけるデータ破損や再現不可能なバグの一般的な原因の一つです。
調整インテント(NSFileCoordinator.ReadingIntent / WritingIntent)は、スレッドまたはプロセスが実行しようとしている操作のタイプを宣言するオブジェクトです。コーディネーターはこれらのインテントを使用してアクセス順序を決定し、競合を解決します。
| インテントの種類 | 説明 | 使用時 |
|---|---|---|
| ReadingIntent | 変更なしのファイル読み取り | ドキュメントを開く、データを読み込む |
| WritingIntent | 内容変更の可能性がある書き込み | ドキュメントの保存、編集 |
| ReadingIntent(URL, options: .withoutChanges) | 変更追跡なしの読み取り | クイックコンテンツプレビュー |
| WritingIntent(URL, options: .contentIndependentMetadataOnly) | メタデータのみの変更 | 日付や属性の更新 |
| WritingIntent(URL, options: .forDeleting) | ファイルの削除 | ユーザーによるドキュメント削除 |
調整ルール:複数の同時読み取りは許可されます(アクティブな書き込みがない場合)。書き込みは排他的です — 書き込み操作中は読み取りも書き込みも許可されません。これはリーダーズ・ライターロックモデルに従いますが、launchdおよびXPCを通じたプロセス間調整の追加サポートがあります。
重要なニュアンス:NSFileCoordinatorは通常のNSDataやFileManagerを介したファイルアクセスを防ぎません — 明示的に調整ブロックでラップされた操作のみを調整します。別のスレッドがコーディネーターなしで直接ファイルにアクセスすると、コーディネーターが防ぐように設計されているまさにその競合状態が発生します。
基本パターンのNSFileCoordinator使用法は3つのステップで構成されます:コーディネーターインスタンスの作成、インテント(読み取りまたは書き込み)の宣言、調整ブロック内での操作の実行。コーディネーターは、他のコーディネーターが同じファイルと同時に動作しないことを保証します。
import Foundation
let coordinator = NSFileCoordinator()
let fileURL = getDocumentURL()
// Safe reading
let readIntent = NSFileCoordinator
.ReadingIntent(url: fileURL)
var content: Data?
var readError: NSError?
coordinator.coordinate(with: readIntent) { error in
if let error = error {
readError = error
return
}
content = try? Data(contentsOf: fileURL)
}
// Safe writing
let writeIntent = NSFileCoordinator
.WritingIntent(url: fileURL)
coordinator.coordinate(with: writeIntent) { error in
guard error == nil else { return }
do {
try newData.write(to: fileURL)
} catch {
Logger.storage.error(
"Write failed: \(error)"
)
}
}
バッチ操作 — コーディネーターはインテントの配列を使用して、1回の操作で複数のファイルを処理できます。これはファイルのセットを1つのトランザクションとして移動、コピー、または削除するのに便利です。いずれかのインテントが実行できない場合、操作全体がエラーでキャンセルされます。
let coordinator = NSFileCoordinator()
let readIntent = NSFileCoordinator
.ReadingIntent(url: sourceURL)
let writeIntent = NSFileCoordinator
.WritingIntent(url: destURL)
coordinator.coordinate(
with: [readIntent, writeIntent]
) { error in
try? FileManager.default
.copyItem(at: sourceURL, to: destURL)
}
非同期調整 — iOS 15以降、NSFileCoordinatorは完了ハンドラー付きの非同期メソッドをサポートしており、呼び出しスレッドをブロックせずに調整を実行できます。これはUIスレッドにとって重要で、調整の同期待機がインターフェイスを数秒間フリーズさせる可能性があります。
NSFilePresenterは、NSFileCoordinatorによって調整されるファイルの変更に関する通知を受け取るためにオブジェクトが実装するプロトコルです。アプリケーションが別のプロセスによって変更される可能性のあるファイルの内容を表示している場合(例えばiCloud Driveが新しいバージョンを同期する)、NSFilePresenterを実装することでタイムリーにインターフェイスを更新できます。
class DocumentPresenter: NSFilePresenter {
let presentedItemURL: URL?
let presentedItemOperationQueue: OperationQueue
init(url: URL) {
presentedItemURL = url
presentedItemOperationQueue = OperationQueue()
}
func presentedItemDidChange() {
DispatchQueue.main.async {
NotificationCenter.default
.post(name: .documentDidChange,
object: self)
}
}
func presentedItemDidMove(to newURL: URL) {
Logger.storage.info(
"File moved to: \(newURL.lastPathComponent)"
)
}
func accommodatePresentedItemDeletion(
completionHandler: @escaping (Error?) -> Void
) {
Logger.storage.warn("File deleted externally")
completionHandler(nil)
}
}
プロトコルメソッド:presentedItemDidChangeはファイルの内容が変更されたときに呼び出され、presentedItemDidMove(to:) — ファイルの移動後、accommodatePresentedItemDeletion — 別のプロセスによるファイル削除前(アプリケーションが適切にファイルを閉じることを許可)。さらに、プロトコルはpresentedItemDidGainVersion:およびpresentedItemDidLoseVersion:を通じたバージョン管理をサポートします。
重要:NSFilePresenterはNSFileCoordinator.addFilePresenter:を介してシステムに登録する必要があります。登録がないと通知は配信されません。登録はアプリケーション起動時に1回行われ、プレゼンターが再作成されても再登録は必要ありません。
常にコーディネーターを使用する Ubiquityコンテナ(iCloud Drive)および拡張機能がアクセス可能なディレクトリ内のファイルに対して。アプリケーションが現在シングルスレッドであっても、将来のアップデートやシステム変更によって並列アクセスが追加される可能性があり、調整の欠如は発見が難しいバグにつながります。
調整ブロック内の時間を最小限に。ブロックの実行中、他のプロセスはファイルにアクセスできません。ブロック内での長時間の操作(複雑なデータ処理、ネットワークリクエスト)はファイルアクセスシステム全体をブロックします。ブロック内ではデータの読み取りまたは書き込みのみを行い、処理はブロック外で行ってください。
デッドロックを避ける:同じファイルに対して別のコーディネーターのブロック内からコーディネーターを呼び出さないでください — 相互デッドロックを引き起こします。ネストされた呼び出しの代わりにバッチ操作(インテントの配列)を使用してください。ネストが必要な場合は、異なるキューまたは異なるURLを使用してください。
objc.io(2024)によると、NSFileCoordinatorを使用する際の典型的なエラーには次のものがあります:完了ハンドラーでのエラー処理の欠如(不完全な操作につながる);書き込みのみの調整で読み取りの調整がない;UIスレッドでの古い同期APIの使用;iCloud DriveでのNSFilePresenterプロトコルの無視。最後のエラーが最も厄介です:アプリケーションはファイルが既に変更されていることに気づかず、古いデータを表示します。
よくある質問
NSFileCoordinatorは、複数のスレッドまたはプロセスからの安全なファイルアクセスのためのFoundationクラスです。ファイルシステムレベルで読み取りおよび書き込み操作を調整することで競合状態を防ぎます。
NSLockは単一プロセス内(スレッド間)でのみ機能します。NSFileCoordinatorはiCloud Driveの同期やFile Provider Extensionを含む、異なるプロセスや拡張機能間のアクセスを調整します。
はい、AppleはUbiquityコンテナ内のすべてのファイル操作にNSFileCoordinatorを使用することを強く推奨しています。コーディネーターがないと、デバイス間の同期中にデータ破損やFile Provider Extensionとの競合が発生する可能性があります。
NSFilePresenterはファイル変更に関する通知を受け取るためのプロトコルです。アプリケーションが他のプロセスによる変更に反応できるようにします:変更時のUI更新、移動の処理、ファイル削除の準備など。
5つのタイプ:ReadingIntent(読み取り)、WritingIntent(書き込み)、.withoutChanges付きReadingIntent(追跡なしの読み取り)、.contentIndependentMetadataOnly付きWritingIntent(メタデータのみ)、.forDeleting付きWritingIntent(削除)。各タイプがファイルアクセスのレベルを定義します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。