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), без координатора виникають стани гонки: процес A читає файл в момент, коли процес B вже частково його перезаписав.
За даними 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 — він координує лише ті операції, які явно обгорнуті в блоки координації. Якщо інший потік звертається до файлу напряму, минаючи координатор, виникають ті самі стани гонки, які координатор покликаний запобігати.
Базовий патерн використання NSFileCoordinator складається з трьох кроків: створити екземпляр координатора, оголосити намір (читання або запис) та виконати операцію всередині блоку координації. Координатор гарантує, що жоден інший координатор не буде одночасно працювати з тим самим файлом.
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)"
)
}
}
Пакетна операція — координатор може обробляти кілька файлів в одній операції, використовуючи масив намірів. Це зручно для переміщення, копіювання або видалення набору файлів як єдиної транзакції. Якщо один з намірів не може бути виконаний, вся операція скасовується з помилкою.
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 для безпечного доступу до файлів з кількох потоків або процесів. Він запобігає станам гонки, узгоджуючи операції читання та запису на рівні файлової системи.
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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також