Το NSFileCoordinator είναι μια κλάση Foundation σε iOS και macOS που παρέχει ασφαλή πρόσβαση σε αρχεία όταν πολλαπλά νήματα, διεργασίες ή επεκτάσεις εργάζονται ταυτόχρονα. Σύμφωνα με το Apple Developer Documentation, 2024, το NSFileCoordinator αποτρέπει τις συνθήκες ανταγωνισμού (race conditions) κατά την ανάγνωση και εγγραφή αρχείων, διασφαλίζοντας ότι καμία διεργασία δεν διαβάζει δεδομένα τη στιγμή που τροποποιούνται από άλλη. Ο συντονιστής χρησιμοποιείται στο iCloud Drive, στο File Provider Extension και σε οποιεσδήποτε πολυνηματικές λειτουργίες αρχείων.
Βασικά σημεία
NSFileCoordinator — είναι ένας μηχανισμός συγχρονισμού πρόσβασης σε αρχεία σε επίπεδο λειτουργικού συστήματος, που παρουσιάστηκε από την Apple στο iOS 5 και macOS 10.7 Lion. Σε αντίθεση με τα παραδοσιακά κλειδώματα (NSLock, pthread_mutex), ο συντονιστής λειτουργεί σε επίπεδο συστήματος αρχείων και μπορεί να συντονίσει την πρόσβαση μεταξύ διαφορετικών διεργασιών, όχι μόνο μεταξύ νημάτων μιας εφαρμογής.
Η ανάγκη για NSFileCoordinator προκύπτει από την αρχιτεκτονική Sandbox στο iOS: κάθε διεργασία (εφαρμογή, επέκταση, υπηρεσία συστήματος) λειτουργεί σε απομονωμένο περιβάλλον με δική της πρόσβαση σε αρχεία. Όταν πολλαπλές διεργασίες προσπαθούν ταυτόχρονα να διαβάσουν και να γράψουν το ίδιο αρχείο (για παράδειγμα, κατά το συγχρονισμό iCloud Drive), χωρίς συντονιστή προκύπτουν συνθήκες ανταγωνισμού: η διεργασία Α διαβάζει το αρχείο τη στιγμή που η διεργασία Β το έχει ήδη μερικώς αντικαταστήσει.
Σύμφωνα με το WWDC 2023, η Apple συνιστά ανεπιφύλακτα τη χρήση του NSFileCoordinator για όλες τις λειτουργίες αρχείων στο Ubiquity container (iCloud Drive) και κατά την εργασία με το File Provider Extension. Η αγνόηση του συντονισμού είναι μια από τις συνηθισμένες αιτίες καταστροφής δεδομένων και μη αναπαραγώγιμων σφαλμάτων σε εφαρμογές iOS.
Πρόθεση συντονισμού (NSFileCoordinator.ReadingIntent / WritingIntent) — είναι ένα αντικείμενο που δηλώνει τον τύπο λειτουργίας που σκοπεύει να εκτελέσει ένα νήμα ή μια διεργασία. Ο συντονιστής χρησιμοποιεί αυτές τις προθέσεις για να καθορίσει τη σειρά πρόσβασης και να επιλύσει συγκρούσεις.
| Τύπος πρόθεσης | Περιγραφή | Πότε να χρησιμοποιείται |
|---|---|---|
| ReadingIntent | Ανάγνωση αρχείου χωρίς αλλαγές | Άνοιγμα εγγράφου, φόρτωση δεδομένων |
| WritingIntent | Εγγραφή με πιθανή αλλαγή περιεχομένου | Αποθήκευση εγγράφου, επεξεργασία |
| ReadingIntent(URL, options: .withoutChanges) | Ανάγνωση χωρίς παρακολούθηση αλλαγών | Γρήγορη προεπισκόπηση περιεχομένου |
| WritingIntent(URL, options: .contentIndependentMetadataOnly) | Αλλαγή μόνο μεταδεδομένων | Ενημέρωση ημερομηνίας ή χαρακτηριστικών |
| WritingIntent(URL, options: .forDeleting) | Διαγραφή αρχείου | Διαγραφή εγγράφου από τον χρήστη |
Κανόνες συντονισμού: πολλαπλές ταυτόχρονες αναγνώσεις επιτρέπονται (αν δεν υπάρχει ενεργή εγγραφή), η εγγραφή είναι αποκλειστική — καμία ανάγνωση ή εγγραφή δεν επιτρέπεται κατά τη διάρκεια της λειτουργίας εγγραφής. Αυτό αντιστοιχεί στο μοντέλο readers-writer lock, αλλά με πρόσθετη υποστήριξη για συντονισμό μεταξύ διεργασιών μέσω launchd και XPC.
Σημαντική απόχρωση: Το NSFileCoordinator δεν εμποδίζει την πρόσβαση στο αρχείο μέσω συνηθισμένου NSData ή FileManager — συντονίζει μόνο εκείνες τις λειτουργίες που είναι ρητά τυλιγμένες σε μπλοκ συντονισμού. Εάν ένα άλλο νήμα αποκτά πρόσβαση στο αρχείο απευθείας, παρακάμπτοντας τον συντονιστή, προκύπτουν ακριβώς εκείνες οι race conditions που ο συντονιστής πρέπει να αποτρέπει.
Βασικό μοτίβο χρήσης του NSFileCoordinator αποτελείται από τρία βήματα: δημιουργία μιας παρουσίας του συντονιστή, δήλωση της πρόθεσης (ανάγνωση ή εγγραφή) και εκτέλεση της λειτουργίας μέσα στο μπλοκ συντονισμού. Ο συντονιστής εγγυάται ότι κανένας άλλος συντονιστής δεν θα εργάζεται ταυτόχρονα με το ίδιο αρχείο.
import Foundation
let coordinator = NSFileCoordinator()
let fileURL = getDocumentURL()
// Ασφαλής ανάγνωση
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)
}
// Ασφαλής εγγραφή
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)"
)
}
}
Μαζική λειτουργία — ο συντονιστής μπορεί να επεξεργαστεί πολλαπλά αρχεία σε μία λειτουργία χρησιμοποιώντας μια σειρά προθέσεων. Αυτό είναι βολικό για μετακίνηση, αντιγραφή ή διαγραφή ενός συνόλου αρχείων ως μία συναλλαγή. Εάν μία από τις προθέσεις δεν μπορεί να εκτελεστεί, ολόκληρη η λειτουργία ακυρώνεται με σφάλμα.
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 υποστηρίζει ασύγχρονες μεθόδους με completion handler, επιτρέποντας την εκτέλεση συντονισμού χωρίς αποκλεισμό του νήματος που καλεί. Αυτό είναι κρίσιμο για το νήμα 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:. Χωρίς καταχώρηση, οι ειδοποιήσεις δεν θα παραδίδονται. Η καταχώρηση γίνεται μία φορά κατά την εκκίνηση της εφαρμογής και δεν απαιτεί εκ νέου καταχώρηση κατά την αναδημιουργία του παρουσιαστή.
Χρησιμοποιείτε πάντα τον συντονιστή για αρχεία στο Ubiquity container (iCloud Drive) και σε καταλόγους προσβάσιμους από επεκτάσεις. Ακόμα κι αν η εφαρμογή είναι επί του παρόντος μονονηματική, μελλοντικές ενημερώσεις ή αλλαγές συστήματος μπορεί να προσθέσουν παράλληλη πρόσβαση και η έλλειψη συντονισμού θα οδηγήσει σε δύσκολα εντοπίσιμα σφάλματα.
Ελαχιστοποιήστε τον χρόνο στο μπλοκ συντονισμού. Ενώ εκτελείται το μπλοκ, άλλες διεργασίες δεν μπορούν να έχουν πρόσβαση στο αρχείο. Οι μακροχρόνιες λειτουργίες εντός του μπλοκ (σύνθετη επεξεργασία δεδομένων, αιτήματα δικτύου) μπλοκάρουν ολόκληρο το σύστημα πρόσβασης αρχείων. Εκτελείτε μόνο ανάγνωση ή εγγραφή δεδομένων εντός του μπλοκ και την επεξεργασία εκτός αυτού.
Αποφύγετε deadlock: μην καλείτε τον συντονιστή από το εσωτερικό ενός μπλοκ άλλου συντονιστή για το ίδιο αρχείο — αυτό θα οδηγήσει σε αμοιβαίο αποκλεισμό. Χρησιμοποιήστε μαζικές λειτουργίες (πίνακας προθέσεων) αντί για ένθετες κλήσεις. Εάν η ένθεση είναι απαραίτητη, χρησιμοποιήστε διαφορετικές ουρές ή διαφορετικά URL.
Σύμφωνα με το objc.io (2024), τα τυπικά σφάλματα κατά την εργασία με το NSFileCoordinator περιλαμβάνουν: έλλειψη διαχείρισης σφαλμάτων στο completion handler (οδηγεί σε ημιτελείς λειτουργίες); συντονισμό μόνο για εγγραφή, αλλά όχι για ανάγνωση; χρήση παρωχημένου σύγχρονου API στο νήμα UI; αγνόηση του πρωτοκόλλου NSFilePresenter κατά την εργασία με iCloud Drive. Το τελευταίο σφάλμα είναι το πιο ύπουλο: η εφαρμογή εμφανίζει παλιά δεδομένα χωρίς να γνωρίζει ότι το αρχείο έχει ήδη αλλάξει.
Συχνές ερωτήσεις
NSFileCoordinator — κλάση Foundation για ασφαλή πρόσβαση σε αρχεία από πολλαπλά νήματα ή διεργασίες. Αποτρέπει τις race conditions συντονίζοντας τις λειτουργίες ανάγνωσης και εγγραφής σε επίπεδο συστήματος αρχείων.
NSLock λειτουργεί μόνο εντός μιας διεργασίας (μεταξύ νημάτων). Το NSFileCoordinator συντονίζει την πρόσβαση μεταξύ διαφορετικών διεργασιών και επεκτάσεων, συμπεριλαμβανομένου του συγχρονισμού iCloud Drive και του File Provider Extension.
Ναι, η Apple συνιστά ανεπιφύλακτα τη χρήση του NSFileCoordinator για όλες τις λειτουργίες αρχείων Ubiquity container. Χωρίς συντονιστή, είναι πιθανή η καταστροφή δεδομένων κατά το συγχρονισμό μεταξύ συσκευών και συγκρούσεις με το File Provider Extension.
NSFilePresenter — πρωτόκολλο για λήψη ειδοποιήσεων αλλαγών αρχείων. Επιτρέπει στην εφαρμογή να αντιδρά σε αλλαγές που γίνονται από άλλες διεργασίες: ενημέρωση UI κατά την τροποποίηση, διαχείριση μετακίνησης ή προετοιμασία για διαγραφή αρχείου.
Πέντε τύπους: ReadingIntent (ανάγνωση), WritingIntent (εγγραφή), ReadingIntent με .withoutChanges (ανάγνωση χωρίς παρακολούθηση), WritingIntent με .contentIndependentMetadataOnly (μόνο μεταδεδομένα) και WritingIntent με .forDeleting (διαγραφή). Κάθε ένα καθορίζει το επίπεδο πρόσβασης στο αρχείο.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης