NSFileCoordinator는 여러 스레드, 프로세스 또는 확장 프로그램이 동시에 작동할 때 안전한 파일 액세스를 보장하는 iOS 및 macOS의 Foundation 클래스입니다. Apple Developer Documentation, 2024에 따르면, NSFileCoordinator는 파일 읽기 및 쓰기 중 경합 상태를 방지하여 한 프로세스가 데이터를 수정하는 동안 다른 프로세스가 데이터를 읽지 않도록 보장합니다. 코디네이터는 iCloud Drive, File Provider Extension 및 모든 멀티스레드 파일 작업에서 사용됩니다.
주요 내용
NSFileCoordinator는 Apple이 iOS 5 및 macOS 10.7 Lion에서 도입한 운영 체제 수준의 파일 액세스 동기화 메커니즘입니다. 기존 잠금(NSLock, pthread_mutex)과 달리 코디네이터는 파일 시스템 수준에서 작동하며 동일한 애플리케이션의 스레드 간뿐만 아니라 다른 프로세스 간의 액세스도 조정할 수 있습니다.
NSFileCoordinator의 필요성은 iOS의 Sandbox 아키텍처에서 발생합니다. 각 프로세스(애플리케이션, 확장 프로그램, 시스템 서비스)는 자체 파일 액세스 권한을 가진 격리된 환경에서 실행됩니다. 여러 프로세스가 동시에 동일한 파일을 읽고 쓰려고 할 때(예: iCloud Drive 동기화 중), 코디네이터가 없으면 경합 상태가 발생합니다. 프로세스 A가 파일을 읽는 동안 프로세스 B가 이미 파일을 부분적으로 덮어쓴 상황입니다.
WWDC 2023에 따르면 Apple은 Ubiquity 컨테이너(iCloud Drive) 및 File Provider Extension 작업 시 모든 파일 작업에 NSFileCoordinator를 사용할 것을 강력히 권장합니다. 조정을 무시하는 것은 iOS 애플리케이션에서 데이터 손상 및 재현 불가능한 버그의 일반적인 원인 중 하나입니다.
조정 인텐트(NSFileCoordinator.ReadingIntent / WritingIntent)는 스레드 또는 프로세스가 수행하려는 작업 유형을 선언하는 객체입니다. 코디네이터는 이러한 인텐트를 사용하여 액세스 순서를 결정하고 충돌을 해결합니다.
| 인텐트 유형 | 설명 | 사용 시기 |
|---|---|---|
| ReadingIntent | 변경 없는 파일 읽기 | 문서 열기, 데이터 로드 |
| WritingIntent | 내용 변경 가능성이 있는 쓰기 | 문서 저장, 편집 |
| ReadingIntent(URL, options: .withoutChanges) | 변경 추적 없는 읽기 | 빠른 내용 미리보기 |
| WritingIntent(URL, options: .contentIndependentMetadataOnly) | 메타데이터만 수정 | 날짜 또는 속성 업데이트 |
| WritingIntent(URL, options: .forDeleting) | 파일 삭제 | 사용자의 문서 삭제 |
조정 규칙: 여러 동시 읽기가 허용됩니다(활성 쓰기가 없는 경우). 쓰기는 배타적입니다 — 쓰기 작업 중에는 읽기나 쓰기가 허용되지 않습니다. 이는 Readers-Writer 잠금 모델을 따르지만 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는 완료 핸들러와 함께 비동기 메서드를 지원하여 호출 스레드를 차단하지 않고 조정을 수행할 수 있습니다. 이는 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 컨테이너(iCloud Drive) 및 확장 프로그램이 액세스할 수 있는 디렉토리의 파일에 대해. 애플리케이션이 현재 단일 스레드이더라도 향후 업데이트나 시스템 변경으로 인해 병렬 액세스가 추가될 수 있으며, 조정 부족은 찾기 어려운 버그로 이어집니다.
조정 블록 내 시간을 최소화하세요. 블록이 실행되는 동안 다른 프로세스는 파일에 액세스할 수 없습니다. 블록 내에서 장기 작업(복잡한 데이터 처리, 네트워크 요청)은 전체 파일 액세스 시스템을 차단합니다. 블록 내에서는 데이터 읽기 또는 쓰기만 수행하고 처리는 블록 외부에서 수행하세요.
데드락을 피하세요: 동일한 파일에 대해 다른 코디네이터의 블록 내에서 코디네이터를 호출하지 마세요 — 상호 데드락이 발생합니다. 중첩 호출 대신 배치 작업(인텐트 배열)을 사용하세요. 중첩이 필요한 경우 다른 큐나 다른 URL을 사용하세요.
objc.io(2024)에 따르면 NSFileCoordinator 작업 시 일반적인 오류는 다음과 같습니다: 완료 핸들러에서 오류 처리 부족(불완전한 작업으로 이어짐); 쓰기만 조정하고 읽기는 조정하지 않음; UI 스레드에서 오래된 동기 API 사용; iCloud Drive 작업 시 NSFilePresenter 프로토콜 무시. 마지막 오류가 가장 교활합니다: 애플리케이션은 파일이 이미 수정되었다는 것을 인지하지 못하고 오래된 데이터를 표시합니다.
자주 묻는 질문
NSFileCoordinator는 여러 스레드 또는 프로세스에서 안전한 파일 액세스를 위한 Foundation 클래스입니다. 파일 시스템 수준에서 읽기 및 쓰기 작업을 조정하여 경합 상태를 방지합니다.
NSLock은 단일 프로세스 내에서만(스레드 간) 작동합니다. NSFileCoordinator는 iCloud Drive 동기화 및 File Provider Extension을 포함하여 다른 프로세스와 확장 프로그램 간의 액세스를 조정합니다.
네, Apple은 Ubiquity 컨테이너의 모든 파일 작업에 NSFileCoordinator를 사용할 것을 강력히 권장합니다. 코디네이터 없이는 기기 간 동기화 중 데이터 손상 및 File Provider Extension과의 충돌이 발생할 수 있습니다.
NSFilePresenter는 파일 변경 알림을 받기 위한 프로토콜입니다. 애플리케이션이 다른 프로세스의 변경에 반응할 수 있게 합니다: 수정 시 UI 업데이트, 이동 처리, 파일 삭제 준비 등.
다섯 가지 유형: ReadingIntent(읽기), WritingIntent(쓰기), .withoutChanges가 있는 ReadingIntent(추적 없는 읽기), .contentIndependentMetadataOnly가 있는 WritingIntent(메타데이터만), .forDeleting이 있는 WritingIntent(삭제). 각각 파일 액세스 수준을 정의합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.