NSFileCoordinator è una classe Foundation in iOS e macOS che garantisce un accesso sicuro ai file quando più thread, processi o estensioni lavorano simultaneamente. Secondo la Documentazione per Sviluppatori Apple, 2024, NSFileCoordinator previene le condizioni di gara durante la lettura e scrittura di file, garantendo che nessun processo legga dati mentre un altro li sta modificando. Il coordinatore è utilizzato in iCloud Drive, File Provider Extension e qualsiasi operazione di file multithread.
Punti chiave
NSFileCoordinator è un meccanismo di sincronizzazione dell'accesso ai file a livello di sistema operativo, introdotto da Apple in iOS 5 e macOS 10.7 Lion. A differenza dei blocchi tradizionali (NSLock, pthread_mutex), il coordinatore opera a livello di filesystem e può coordinare l'accesso tra diversi processi, non solo tra thread della stessa applicazione.
La necessità di NSFileCoordinator deriva dall'architettura Sandbox in iOS: ogni processo (applicazione, estensione, servizio di sistema) viene eseguito in un ambiente isolato con il proprio accesso ai file. Quando più processi tentano di leggere e scrivere lo stesso file simultaneamente (ad esempio, durante la sincronizzazione di iCloud Drive), senza un coordinatore si verificano condizioni di gara: il processo A legge il file mentre il processo B lo ha già parzialmente sovrascritto.
Secondo la WWDC 2023, Apple raccomanda vivamente di utilizzare NSFileCoordinator per tutte le operazioni sui file nel container Ubiquity (iCloud Drive) e quando si lavora con File Provider Extension. Ignorare il coordinamento è una delle cause comuni di corruzione dei dati e bug non riproducibili nelle applicazioni iOS.
Intenzione di coordinamento (NSFileCoordinator.ReadingIntent / WritingIntent) è un oggetto che dichiara il tipo di operazione che un thread o processo intende eseguire. Il coordinatore utilizza queste intenzioni per determinare l'ordine di accesso e risolvere i conflitti.
| Tipo di intenzione | Descrizione | Quando usarlo |
|---|---|---|
| ReadingIntent | Lettura del file senza modifiche | Aprire un documento, caricare dati |
| WritingIntent | Scrittura con possibile modifica del contenuto | Salvare un documento, modificare |
| ReadingIntent(URL, options: .withoutChanges) | Lettura senza tracciamento delle modifiche | Anteprima rapida del contenuto |
| WritingIntent(URL, options: .contentIndependentMetadataOnly) | Modifica solo dei metadati | Aggiornare data o attributi |
| WritingIntent(URL, options: .forDeleting) | Eliminazione del file | Eliminazione del documento da parte dell'utente |
Regole di coordinamento: sono consentite più letture simultanee (se non c'è scrittura attiva), la scrittura è esclusiva — nessuna lettura o scrittura è consentita durante un'operazione di scrittura. Questo segue il modello di blocco lettori-scrittori, ma con supporto aggiuntivo per il coordinamento tra processi tramite launchd e XPC.
Sfumatura importante: NSFileCoordinator non impedisce l'accesso ai file tramite NSData o FileManager normali — coordina solo le operazioni che sono esplicitamente racchiuse in blocchi di coordinamento. Se un altro thread accede direttamente al file senza il coordinatore, si verificano esattamente le condizioni di gara che il coordinatore è progettato per prevenire.
Schema di base per l'utilizzo di NSFileCoordinator consiste in tre passaggi: creare un'istanza del coordinatore, dichiarare un'intenzione (lettura o scrittura) ed eseguire l'operazione all'interno di un blocco di coordinamento. Il coordinatore garantisce che nessun altro coordinatore lavori simultaneamente con lo stesso file.
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)"
)
}
}
Operazione batch — il coordinatore può gestire più file in una singola operazione utilizzando un array di intenzioni. Questo è comodo per spostare, copiare o eliminare un insieme di file come un'unica transazione. Se una delle intenzioni non può essere soddisfatta, l'intera operazione viene annullata con un errore.
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)
}
Coordinamento asincrono — a partire da iOS 15, NSFileCoordinator supporta metodi asincroni con un gestore di completamento, consentendo il coordinamento senza bloccare il thread chiamante. Questo è fondamentale per il thread UI, dove l'attesa sincrona per il coordinamento può congelare l'interfaccia per secondi.
NSFilePresenter è un protocollo che un oggetto implementa per ricevere notifiche sulle modifiche ai file coordinati da NSFileCoordinator. Se la tua applicazione visualizza il contenuto di un file che può essere modificato da un altro processo (ad esempio, iCloud Drive sincronizza una nuova versione), implementare NSFilePresenter consente di aggiornare tempestivamente l'interfaccia.
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)
}
}
Metodi del protocollo: presentedItemDidChange viene chiamato quando il contenuto del file cambia, presentedItemDidMove(to:) — dopo lo spostamento del file, accommodatePresentedItemDeletion — prima dell'eliminazione del file da parte di un altro processo (consente all'applicazione di chiudere correttamente il file). Inoltre, il protocollo supporta il versionamento tramite presentedItemDidGainVersion: e presentedItemDidLoseVersion:.
Importante: NSFilePresenter deve essere registrato nel sistema tramite NSFileCoordinator.addFilePresenter:. Senza registrazione, le notifiche non verranno consegnate. La registrazione viene effettuata una volta all'avvio dell'applicazione e non richiede una nuova registrazione quando il presentatore viene ricreato.
Usa sempre il coordinatore per i file nel container Ubiquity (iCloud Drive) e nelle directory accessibili alle estensioni. Anche se l'applicazione è attualmente single-thread, futuri aggiornamenti o modifiche di sistema potrebbero aggiungere accesso parallelo, e la mancanza di coordinamento porterà a bug difficili da trovare.
Minimizza il tempo all'interno del blocco di coordinamento. Mentre il blocco è in esecuzione, altri processi non possono accedere al file. Operazioni lunghe all'interno del blocco (elaborazione complessa di dati, richieste di rete) bloccano l'intero sistema di accesso ai file. Esegui solo lettura o scrittura di dati all'interno del blocco e l'elaborazione all'esterno.
Evita deadlock: non chiamare il coordinatore dall'interno del blocco di un altro coordinatore per lo stesso file — ciò causerà un deadlock reciproco. Utilizza operazioni batch (array di intenzioni) invece di chiamate annidate. Se l'annidamento è necessario, utilizza code diverse o URL diversi.
Secondo objc.io (2024), gli errori tipici quando si lavora con NSFileCoordinator includono: mancanza di gestione degli errori nel gestore di completamento (porta a operazioni incomplete); coordinamento solo per le scritture ma non per le letture; utilizzo della vecchia API sincrona sul thread UI; ignorare il protocollo NSFilePresenter quando si lavora con iCloud Drive. L'ultimo errore è il più insidioso: l'applicazione mostra dati obsoleti senza rendersi conto che il file è già stato modificato.
Domande frequenti
NSFileCoordinator è una classe Foundation per l'accesso sicuro ai file da più thread o processi. Previene le condizioni di gara coordinando le operazioni di lettura e scrittura a livello di filesystem.
NSLock funziona solo all'interno di un singolo processo (tra thread). NSFileCoordinator coordina l'accesso tra diversi processi ed estensioni, inclusa la sincronizzazione di iCloud Drive e File Provider Extension.
Sì, Apple raccomanda vivamente di utilizzare NSFileCoordinator per tutte le operazioni sui file nel container Ubiquity. Senza il coordinatore, possono verificarsi corruzione dei dati durante la sincronizzazione tra dispositivi e conflitti con File Provider Extension.
NSFilePresenter è un protocollo per ricevere notifiche sulle modifiche ai file. Consente all'applicazione di reagire alle modifiche apportate da altri processi: aggiornare l'UI in caso di modifica, gestire lo spostamento o prepararsi all'eliminazione del file.
Cinque tipi: ReadingIntent (lettura), WritingIntent (scrittura), ReadingIntent con .withoutChanges (lettura senza tracciamento), WritingIntent con .contentIndependentMetadataOnly (solo metadati) e WritingIntent con .forDeleting (eliminazione). Ognuno definisce il livello di accesso al file.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche