NSFileCoordinator to klasa Foundation w iOS i macOS, zapewniająca bezpieczny dostęp do plików przy jednoczesnej pracy wielu wątków, procesów lub rozszerzeń. Według Apple Developer Documentation, 2024, NSFileCoordinator zapobiega stanom wyścigu (race conditions) podczas odczytu i zapisu plików, gwarantując, że żaden proces nie odczyta danych w momencie ich zmiany przez inny. Koordynator jest używany w iCloud Drive, File Provider Extension i wszelkich wielowątkowych operacjach plikowych.
Najważniejsze
NSFileCoordinator — to mechanizm synchronizacji dostępu do plików na poziomie systemu operacyjnego, wprowadzony przez Apple w iOS 5 i macOS 10.7 Lion. W przeciwieństwie do tradycyjnych blokad (NSLock, pthread_mutex), koordynator działa na poziomie systemu plików i może koordynować dostęp między różnymi procesami, a nie tylko między wątkami jednej aplikacji.
Konieczność stosowania NSFileCoordinator wynika z architektury Sandbox w iOS: każdy proces (aplikacja, rozszerzenie, serwis systemowy) działa w izolowanym środowisku z własnym dostępem do plików. Gdy wiele procesów próbuje jednocześnie czytać i zapisywać ten sam plik (na przykład przy synchronizacji iCloud Drive), bez koordynatora powstają stany wyścigu: proces A odczytuje plik w momencie, gdy proces B już częściowo go nadpisał.
Według WWDC 2023, Apple zdecydowanie zaleca używanie NSFileCoordinator dla wszystkich operacji na plikach w Ubiquity container (iCloud Drive) oraz przy pracy z File Provider Extension. Ignorowanie koordynacji — jedna z częstych przyczyn uszkodzenia danych i nieodtwarzalnych błędów w aplikacjach iOS.
Intencja koordynacyjna (NSFileCoordinator.ReadingIntent / WritingIntent) — to obiekt deklarujący typ operacji, którą planuje wykonać wątek lub proces. Koordynator używa tych intencji do określenia kolejności dostępu i rozwiązywania konfliktów.
| Rodzaj intencji | Opis | Kiedy używać |
|---|---|---|
| ReadingIntent | Odczyt pliku bez zmian | Otwieranie dokumentu, ładowanie danych |
| WritingIntent | Zapis z możliwą zmianą zawartości | Zapisywanie dokumentu, edycja |
| ReadingIntent(URL, options: .withoutChanges) | Odczyt bez śledzenia zmian | Szybki podgląd zawartości |
| WritingIntent(URL, options: .contentIndependentMetadataOnly) | Zmiana tylko metadanych | Aktualizacja daty lub atrybutów |
| WritingIntent(URL, options: .forDeleting) | Usunięcie pliku | Usuwanie dokumentu przez użytkownika |
Zasady koordynacji: wiele równoczesnych odczytów jest dozwolonych (jeśli nie ma aktywnego zapisu), zapis jest ekskluzywny — żaden odczyt ani zapis nie są dozwolone podczas operacji zapisu. Odpowiada to modelowi readers-writer lock, ale z dodatkowym wsparciem koordynacji międzyprocesowej przez launchd i XPC.
Ważny niuans: NSFileCoordinator nie zapobiega dostępowi do pliku przez zwykłe NSData lub FileManager — koordynuje tylko te operacje, które są jawnie opakowane w bloki koordynacji. Jeśli inny wątek uzyskuje dostęp do pliku bezpośrednio, omijając koordynatora, powstają te same race conditions, które koordynator ma zapobiegać.
Podstawowy wzorzec użycia NSFileCoordinator składa się z trzech kroków: utworzenie instancji koordynatora, zadeklarowanie intencji (odczyt lub zapis) i wykonanie operacji wewnątrz bloku koordynacji. Koordynator gwarantuje, że żaden inny koordynator nie będzie jednocześnie pracować z tym samym plikiem.
import Foundation
let coordinator = NSFileCoordinator()
let fileURL = getDocumentURL()
// Bezpieczne odczytywanie
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)
}
// Bezpieczne zapisywanie
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)"
)
}
}
Operacja zbiorcza — koordynator może obsługiwać wiele plików w jednej operacji, używając tablicy intencji. Jest to wygodne do przenoszenia, kopiowania lub usuwania zestawu plików jako pojedynczej transakcji. Jeśli jedna z intencji nie może zostać wykonana, cała operacja jest anulowana z błędem.
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)
}
Koordynacja asynchroniczna — od iOS 15 NSFileCoordinator obsługuje metody asynchroniczne z completion handler, co pozwala na wykonanie koordynacji bez blokowania wątku wywołującego. Jest to krytycznie ważne dla wątku UI, gdzie synchroniczne oczekiwanie na koordynację może spowodować zawieszenie interfejsu na sekundy.
NSFilePresenter — to protokół, który obiekt implementuje w celu otrzymywania powiadomień o zmianach plików koordynowanych przez NSFileCoordinator. Jeśli twoja aplikacja wyświetla zawartość pliku, który może być zmieniony przez inny proces (na przykład iCloud Drive synchronizuje nową wersję), implementacja NSFilePresenter pozwala na bieżąco aktualizować interfejs.
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 protokołu: presentedItemDidChange jest wywoływana przy zmianie zawartości pliku, presentedItemDidMove(to:) — po przeniesieniu pliku, accommodatePresentedItemDeletion — przed usunięciem pliku przez inny proces (pozwala aplikacji poprawnie zamknąć plik). Dodatkowo protokół obsługuje wersjonowanie przez presentedItemDidGainVersion: i presentedItemDidLoseVersion:.
Ważne: NSFilePresenter musi być zarejestrowany w systemie przez NSFileCoordinator.addFilePresenter:. Bez rejestracji powiadomienia nie będą dostarczane. Rejestracja jest wykonywana jednorazowo przy starcie aplikacji i nie wymaga ponownej rejestracji przy odtworzeniu prezentera.
Zawsze używaj koordynatora dla plików w Ubiquity container (iCloud Drive) i katalogach dostępnych dla rozszerzeń. Nawet jeśli obecnie aplikacja jest jednowątkowa, przyszłe aktualizacje lub zmiany systemowe mogą dodać równoległy dostęp, a brak koordynacji doprowadzi do trudnych do wychwycenia błędów.
Minimalizuj czas w bloku koordynacji. Podczas wykonywania bloku inne procesy nie mogą uzyskać dostępu do pliku. Długotrwałe operacje wewnątrz bloku (złożone przetwarzanie danych, żądania sieciowe) blokują cały system dostępu do plików. Wykonuj tylko odczyt lub zapis danych wewnątrz bloku, a przetwarzanie — poza nim.
Unikaj deadlock-ów: nie wywołuj koordynatora z wnętrza bloku innego koordynatora dla tego samego pliku — doprowadzi to do wzajemnej blokady. Używaj operacji zbiorczych (tablica intencji) zamiast zagnieżdżonych wywołań. Jeśli zagnieżdżenie jest konieczne, używaj różnych kolejek lub różnych URL-i.
Według objc.io (2024), typowe błędy przy pracy z NSFileCoordinator obejmują: brak obsługi błędów w completion handler (prowadzi do nieukończonych operacji); koordynacja tylko dla zapisu, ale nie dla odczytu; używanie przestarzałego synchronicznego API w wątku UI; ignorowanie protokołu NSFilePresenter przy pracy z iCloud Drive. Ostatni błąd jest najpodstępniejszy: aplikacja pokazuje nieaktualne dane, nie wiedząc, że plik został już zmieniony.
Często zadawane pytania
NSFileCoordinator — klasa Foundation do bezpiecznego dostępu do plików z wielu wątków lub procesów. Zapobiega race conditions, koordynując operacje odczytu i zapisu na poziomie systemu plików.
NSLock działa tylko wewnątrz jednego procesu (między wątkami). NSFileCoordinator koordynuje dostęp między różnymi procesami i rozszerzeniami, w tym synchronizację iCloud Drive i File Provider Extension.
Tak, Apple zdecydowanie zaleca używanie NSFileCoordinator dla wszystkich operacji na plikach Ubiquity container. Bez koordynatora możliwe jest uszkodzenie danych podczas synchronizacji między urządzeniami i konflikty z File Provider Extension.
NSFilePresenter — protokół do otrzymywania powiadomień o zmianach plików. Pozwala aplikacji reagować na zmiany dokonane przez inne procesy: aktualizować UI przy modyfikacji, obsługiwać przeniesienie lub przygotować się do usunięcia pliku.
Pięć typów: ReadingIntent (odczyt), WritingIntent (zapis), ReadingIntent z .withoutChanges (odczyt bez śledzenia), WritingIntent z .contentIndependentMetadataOnly (tylko metadane) i WritingIntent z .forDeleting (usunięcie). Każdy określa poziom dostępu do pliku.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również