NSFileCoordinator je třída Foundation v iOS a macOS, která zajišťuje bezpečný přístup k souborům při současné práci více vláken, procesů nebo rozšíření. Podle Apple Developer Documentation, 2024, NSFileCoordinator zabraňuje race conditions při čtení a zápisu souborů a zaručuje, že žádný proces nečte data v okamžiku jejich změny jiným procesem. Koordinátor se používá v iCloud Drive, File Provider Extension a všech vícevláknových souborových operacích.
Hlavní body
NSFileCoordinator — je mechanismus synchronizace přístupu k souborům na úrovni operačního systému, představený Apple v iOS 5 a macOS 10.7 Lion. Na rozdíl od tradičních zámků (NSLock, pthread_mutex), koordinátor pracuje na úrovni souborového systému a může koordinovat přístup mezi různými procesy, nejen mezi vlákny jedné aplikace.
Potřeba NSFileCoordinator vyplývá z architektury Sandbox v iOS: každý proces (aplikace, rozšíření, systémová služba) pracuje v izolovaném prostředí s vlastním přístupem k souborům. Když se několik procesů pokouší současně číst a zapisovat stejný soubor (například při synchronizaci iCloud Drive), bez koordinátora vznikají race conditions: proces A čte soubor v okamžiku, kdy jej proces B již částečně přepsal.
Podle WWDC 2023 Apple důrazně doporučuje používat NSFileCoordinator pro všechny operace se soubory v Ubiquity container (iCloud Drive) a při práci s File Provider Extension. Ignorování koordinace je jedním z častých příčin poškození dat a nereprodukovatelných chyb v iOS aplikacích.
Koordinační záměr (NSFileCoordinator.ReadingIntent / WritingIntent) — je objekt, který deklaruje typ operace, kterou vlákno nebo proces plánuje provést. Koordinátor používá tyto záměry k určení pořadí přístupu a řešení konfliktů.
| Typ záměru | Popis | Kdy použít |
|---|---|---|
| ReadingIntent | Čtení souboru bez změn | Otevření dokumentu, načítání dat |
| WritingIntent | Zápis s možnou změnou obsahu | Uložení dokumentu, editace |
| ReadingIntent(URL, options: .withoutChanges) | Čtení bez sledování změn | Rychlý náhled obsahu |
| WritingIntent(URL, options: .contentIndependentMetadataOnly) | Změna pouze metadat | Aktualizace data nebo atributů |
| WritingIntent(URL, options: .forDeleting) | Smazání souboru | Smazání dokumentu uživatelem |
Pravidla koordinace: více současných čtení je povoleno (pokud není aktivní zápis), zápis je exkluzivní — během operace zápisu není povoleno žádné čtení ani zápis. To odpovídá modelu readers-writer lock, ale s dodatečnou podporou pro meziprocesovou koordinaci prostřednictvím launchd a XPC.
Důležitý detail: NSFileCoordinator nebrání přístupu k souboru prostřednictvím běžného NSData nebo FileManager — koordinuje pouze ty operace, které jsou explicitně zabaleny do koordinačních bloků. Pokud jiné vlákno přistupuje k souboru přímo, obcházejíc koordinátora, vznikají právě ty race conditions, kterým má koordinátor zabránit.
Základní vzor použití NSFileCoordinator sestává ze tří kroků: vytvoření instance koordinátora, deklarování záměru (čtení nebo zápis) a provedení operace uvnitř koordinačního bloku. Koordinátor zaručuje, že žádný jiný koordinátor nebude současně pracovat se stejným souborem.
import Foundation
let coordinator = NSFileCoordinator()
let fileURL = getDocumentURL()
// Bezpečné čtení
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)
}
// Bezpečný zápis
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)"
)
}
}
Dávková operace — koordinátor může zpracovat více souborů v jedné operaci pomocí pole záměrů. To je vhodné pro přesun, kopírování nebo mazání sady souborů jako jedné transakce. Pokud jeden ze záměrů nelze provést, celá operace je zrušena s chybou.
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)
}
Asynchronní koordinace — od iOS 15 NSFileCoordinator podporuje asynchronní metody s completion handler, což umožňuje provádět koordinaci bez blokování volajícího vlákna. To je kriticky důležité pro UI vlákno, kde synchronní čekání na koordinaci může způsobit zamrznutí rozhraní na sekundy.
NSFilePresenter — je protokol, který objekt implementuje pro přijímání oznámení o změnách souborů koordinovaných NSFileCoordinator. Pokud vaše aplikace zobrazuje obsah souboru, který může být změněn jiným procesem (například iCloud Drive synchronizuje novou verzi), implementace NSFilePresenter umožňuje včasnou aktualizaci rozhraní.
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)
}
}
Metody protokolu: presentedItemDidChange je volána při změně obsahu souboru, presentedItemDidMove(to:) — po přesunu souboru, accommodatePresentedItemDeletion — před smazáním souboru jiným procesem (umožňuje aplikaci správně zavřít soubor). Dále protokol podporuje verzování prostřednictvím presentedItemDidGainVersion: a presentedItemDidLoseVersion:.
Důležité: NSFilePresenter musí být zaregistrován v systému prostřednictvím NSFileCoordinator.addFilePresenter:. Bez registrace nebudou oznámení doručována. Registrace se provádí jednou při spuštění aplikace a nevyžaduje opětovnou registraci při opětovném vytvoření presenteru.
Vždy používejte koordinátora pro soubory v Ubiquity container (iCloud Drive) a adresářích přístupných rozšířením. I když je aplikace aktuálně jednovláknová, budoucí aktualizace nebo systémové změny mohou přidat paralelní přístup a nedostatek koordinace povede k obtížně nalezitelným chybám.
Minimalizujte čas v koordinačním bloku. Během provádění bloku nemohou ostatní procesy přistupovat k souboru. Dlouhé operace uvnitř bloku (složité zpracování dat, síťové požadavky) blokují celý systém přístupu k souborům. Provádějte pouze čtení nebo zápis dat uvnitř bloku a zpracování mimo něj.
Vyhněte se deadlockům: nevolajte koordinátora zevnitř bloku jiného koordinátora pro stejný soubor — to povede k vzájemnému blokování. Používejte dávkové operace (pole záměrů) namísto vnořených volání. Pokud je vnoření nezbytné, použijte různé fronty nebo různá URL.
Podle objc.io (2024) typické chyby při práci s NSFileCoordinator zahrnují: chybějící zpracování chyb v completion handler (vede k nedokončeným operacím); koordinace pouze pro zápis, ale ne pro čtení; používání zastaralého synchronního API v UI vlákně; ignorování protokolu NSFilePresenter při práci s iCloud Drive. Poslední chyba je nejzákeřnější: aplikace zobrazuje zastaralá data, aniž by věděla, že soubor již byl změněn.
Často kladené otázky
NSFileCoordinator — třída Foundation pro bezpečný přístup k souborům z více vláken nebo procesů. Zabraňuje race conditions koordinací operací čtení a zápisu na úrovni souborového systému.
NSLock funguje pouze uvnitř jednoho procesu (mezi vlákny). NSFileCoordinator koordinuje přístup mezi různými procesy a rozšířeními, včetně synchronizace iCloud Drive a File Provider Extension.
Ano, Apple důrazně doporučuje používat NSFileCoordinator pro všechny operace se soubory Ubiquity container. Bez koordinátora je možné poškození dat při synchronizaci mezi zařízeními a konflikty s File Provider Extension.
NSFilePresenter — protokol pro přijímání oznámení o změnách souborů. Umožňuje aplikaci reagovat na změny provedené jinými procesy: aktualizovat UI při změně, zpracovat přesun nebo se připravit na smazání souboru.
Pět typů: ReadingIntent (čtení), WritingIntent (zápis), ReadingIntent s .withoutChanges (čtení bez sledování), WritingIntent s .contentIndependentMetadataOnly (pouze metadata) a WritingIntent s .forDeleting (smazání). Každý určuje úroveň přístupu k souboru.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také