NSFileCoordinator este o clasă Foundation în iOS și macOS, care asigură accesul sigur la fișiere atunci când mai multe fire, procese sau extensii lucrează simultan. Potrivit Apple Developer Documentation, 2024, NSFileCoordinator previne condițiile de cursă (race conditions) la citirea și scrierea fișierelor, garantând că niciun proces nu citește datele în momentul modificării lor de către altul. Coordonatorul este utilizat în iCloud Drive, File Provider Extension și orice operațiuni de fișiere multi-thread.
Puncte cheie
NSFileCoordinator — este un mecanism de sincronizare a accesului la fișiere la nivelul sistemului de operare, introdus de Apple în iOS 5 și macOS 10.7 Lion. Spre deosebire de blocările tradiționale (NSLock, pthread_mutex), coordonatorul operează la nivelul sistemului de fișiere și poate coordona accesul între diferite procese, nu doar între firele aceleiași aplicații.
Necesitatea NSFileCoordinator provine din arhitectura Sandbox în iOS: fiecare proces (aplicație, extensie, serviciu de sistem) funcționează într-un mediu izolat cu propriul acces la fișiere. Când mai multe procese încearcă să citească și să scrie simultan același fișier (de exemplu, la sincronizarea iCloud Drive), fără coordonator apar condiții de cursă: procesul A citește fișierul în momentul în care procesul B l-a suprascris deja parțial.
Potrivit WWDC 2023, Apple recomandă insistent utilizarea NSFileCoordinator pentru toate operațiunile cu fișiere în Ubiquity container (iCloud Drive) și la lucrul cu File Provider Extension. Ignorarea coordonării este una dintre cauzele frecvente ale deteriorării datelor și erorilor nereproductibile în aplicațiile iOS.
Intenția de coordonare (NSFileCoordinator.ReadingIntent / WritingIntent) — este un obiect care declară tipul operației pe care un fir sau proces intenționează să o execute. Coordonatorul folosește aceste intenții pentru a determina ordinea accesului și a rezolva conflictele.
| Tipul intenției | Descriere | Când să utilizați |
|---|---|---|
| ReadingIntent | Citirea fișierului fără modificări | Deschiderea documentului, încărcarea datelor |
| WritingIntent | Scrierea cu posibila modificare a conținutului | Salvarea documentului, editare |
| ReadingIntent(URL, options: .withoutChanges) | Citirea fără urmărirea modificărilor | Vizualizare rapidă a conținutului |
| WritingIntent(URL, options: .contentIndependentMetadataOnly) | Modificarea doar a metadatelor | Actualizarea datei sau atributelor |
| WritingIntent(URL, options: .forDeleting) | Ștergerea fișierului | Ștergerea documentului de către utilizator |
Reguli de coordonare: lecturile simultane multiple sunt permise (dacă nu există o scriere activă), scrierea este exclusivă — nicio citire sau scriere nu este permisă în timpul operației de scriere. Aceasta corespunde modelului readers-writer lock, dar cu suport suplimentar pentru coordonarea inter-proces prin launchd și XPC.
Nuanță importantă: NSFileCoordinator nu împiedică accesul la fișier prin NSData sau FileManager obișnuit — coordonează doar operațiunile care sunt explicit încapsulate în blocuri de coordonare. Dacă un alt fir accesează fișierul direct, ocolind coordonatorul, apar exact acele race conditions pe care coordonatorul ar trebui să le prevină.
Modelul de bază de utilizare a NSFileCoordinator constă în trei pași: crearea unei instanțe a coordonatorului, declararea intenției (citire sau scriere) și executarea operației în interiorul blocului de coordonare. Coordonatorul garantează că niciun alt coordonator nu va lucra simultan cu același fișier.
import Foundation
let coordinator = NSFileCoordinator()
let fileURL = getDocumentURL()
// Citire sigură
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)
}
// Scriere sigură
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)"
)
}
}
Operația în lot — coordonatorul poate gestiona mai multe fișiere într-o singură operație, utilizând un tablou de intenții. Acest lucru este convenabil pentru mutarea, copierea sau ștergerea unui set de fișiere ca o singură tranzacție. Dacă una dintre intenții nu poate fi executată, întreaga operație este anulată cu o eroare.
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)
}
Coordonarea asincronă — începând cu iOS 15, NSFileCoordinator suportă metode asincrone cu completion handler, permițând executarea coordonării fără blocarea firului apelant. Acest lucru este critic pentru firul UI, unde așteptarea sincronă a coordonării poate cauza înghețarea interfeței pentru secunde.
NSFilePresenter — este un protocol pe care un obiect îl implementează pentru a primi notificări despre modificările fișierelor coordonate de NSFileCoordinator. Dacă aplicația dvs. afișează conținutul unui fișier care poate fi modificat de un alt proces (de exemplu, iCloud Drive sincronizează o nouă versiune), implementarea NSFilePresenter permite actualizarea la timp a interfeței.
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)
}
}
Metodele protocolului: presentedItemDidChange este apelată la modificarea conținutului fișierului, presentedItemDidMove(to:) — după mutarea fișierului, accommodatePresentedItemDeletion — înainte de ștergerea fișierului de către un alt proces (permite aplicației să închidă corect fișierul). În plus, protocolul suportă versionarea prin presentedItemDidGainVersion: și presentedItemDidLoseVersion:.
Important: NSFilePresenter trebuie înregistrat în sistem prin NSFileCoordinator.addFilePresenter:. Fără înregistrare, notificările nu vor fi livrate. Înregistrarea se efectuează o singură dată la pornirea aplicației și nu necesită reînregistrare la recrearea prezentatorului.
Utilizați întotdeauna coordonatorul pentru fișierele din Ubiquity container (iCloud Drive) și directoarele accesibile extensiilor. Chiar dacă în prezent aplicația este single-thread, actualizările viitoare sau modificările sistemului pot adăuga acces paralel, iar lipsa coordonării va duce la erori greu de depistat.
Minimizați timpul în blocul de coordonare. În timp ce blocul se execută, alte procese nu pot accesa fișierul. Operațiunile lungi în interiorul blocului (procesare complexă de date, cereri de rețea) blochează întregul sistem de acces la fișiere. Efectuați doar citirea sau scrierea datelor în interiorul blocului, iar procesarea — în afara acestuia.
Evitați deadlock-urile: nu apelați coordonatorul din interiorul blocului unui alt coordonator pentru același fișier — acest lucru va duce la o blocare mutuală. Utilizați operațiuni în lot (tablou de intenții) în loc de apeluri imbricate. Dacă imbricarea este necesară, utilizați cozi diferite sau URL-uri diferite.
Potrivit objc.io (2024), erorile tipice la lucrul cu NSFileCoordinator includ: lipsa gestionării erorilor în completion handler (duce la operațiuni neterminate); coordonarea doar pentru scriere, dar nu și pentru citire; utilizarea API-ului sincron vechi în firul UI; ignorarea protocolului NSFilePresenter la lucrul cu iCloud Drive. Ultima eroare este cea mai perfidă: aplicația afișează date învechite, fără să știe că fișierul a fost deja modificat.
Întrebări frecvente
NSFileCoordinator — clasă Foundation pentru acces sigur la fișiere din mai multe fire sau procese. Previne race conditions, coordonând operațiunile de citire și scriere la nivelul sistemului de fișiere.
NSLock funcționează doar în interiorul unui singur proces (între fire). NSFileCoordinator coordonează accesul între diferite procese și extensii, inclusiv sincronizarea iCloud Drive și File Provider Extension.
Da, Apple recomandă insistent utilizarea NSFileCoordinator pentru toate operațiunile cu fișiere Ubiquity container. Fără coordonator, sunt posibile deteriorări ale datelor în timpul sincronizării între dispozitive și conflicte cu File Provider Extension.
NSFilePresenter — protocol pentru primirea notificărilor de modificări ale fișierelor. Permite aplicației să reacționeze la modificările făcute de alte procese: actualizarea UI la modificare, gestionarea mutării sau pregătirea pentru ștergerea fișierului.
Cinci tipuri: ReadingIntent (citire), WritingIntent (scriere), ReadingIntent cu .withoutChanges (citire fără urmărire), WritingIntent cu .contentIndependentMetadataOnly (doar metadate) și WritingIntent cu .forDeleting (ștergere). Fiecare definește nivelul de acces la fișier.
Concluzii
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și