What are the FileManager basics for saving a document: creating directories, replacing a file, and the resource values you set on it?
Predict first
What do replace, backup exclusion and a missing directory do?
import Foundation
let fm = FileManager.default
let dir = fm.temporaryDirectory.appendingPathComponent(UUID().uuidString)
try fm.createDirectory(at: dir, withIntermediateDirectories: true)
let target = dir.appendingPathComponent("doc.json")
try Data("v1".utf8).write(to: target)
let replacement = dir.appendingPathComponent("doc.tmp")
try Data("v2".utf8).write(to: replacement)
let result = try fm.replaceItemAt(target, withItemAt: replacement)
print("replaced:", String(decoding: try Data(contentsOf: target), as: UTF8.self),
"temp file gone:", !fm.fileExists(atPath: replacement.path),
"result is the target:", result?.lastPathComponent == "doc.json")
var u = target
var rv = URLResourceValues(); rv.isExcludedFromBackup = true
try u.setResourceValues(rv)
print("excluded from backup:", try u.resourceValues(forKeys: [.isExcludedFromBackupKey]).isExcludedFromBackup ?? false)
print("size:", try u.resourceValues(forKeys: [.fileSizeKey]).fileSize ?? -1)
do { try Data("x".utf8).write(to: dir.appendingPathComponent("missing/sub/file")) }
catch { print("write into a missing directory fails, code", (error as NSError).code) }
try? fm.removeItem(at: dir)
- replaced: v2 temp file gone: true result is the target: true / excluded from backup: true / size: 2 / write into a missing directory fails, code 4
- replaced: v1 …
- the write creates the missing directories
Mental model. Saving a document is like editing a contract: write the new copy on a fresh sheet, and only when it is complete swap it for the old one, so a spill halfway never destroys the original.
Three small rules prevent most file bugs. First, the directory must exist: writing to …/missing/sub/file fails (the check reports a cocoa error, code 4, “no such file”), because Data.write(to:) creates the file but not its parents. Create the folder with createDirectory(at:withIntermediateDirectories: true) once (it succeeds if it already exists), typically under Application Support, which also does not exist until you create it (the system URL lookup can create it for you with create: true). Second, replace, do not overwrite in place: write the new version to a temporary file in the same volume, then replaceItemAt(_:withItemAt:) swaps it in atomically and removes the temporary file (the check shows the new contents and the temp file gone); write(to:options: .atomic) does the same internally for a single write. A crash mid-write then leaves the old complete file, not a torn one (Q339).
Third, resource values describe the file to the system: isExcludedFromBackup stops regenerable data from consuming the user’s iCloud backup (set it on caches you must keep in Application Support; the check sets and reads it back, true), and fileSizeKey gives the size without reading the file. Put each kind of data where it belongs: user documents in Documents, app data in Application Support, regenerable data in Caches (the system may delete it), scratch in the temporary directory. Remember URL values are copies: set resource values on a var URL. Do file I/O off the main thread, use NSFileCoordinator for shared files (iCloud Drive, App Groups with multiple writers), and handle the “file does not exist yet” case on first launch as normal, not as an error.
replaceItemAt swaps the file and removes the temp; resource values round-trip; a missing parent directory is an error.
Common traps
- Writing into a directory you assumed exists. Application Support is not created for you until you ask.
- Overwriting a file in place. A crash or full disk can leave it truncated; use atomic replace.
They might follow up with
- When do I need
NSFileCoordinator? When several processes or the system (iCloud, extensions) may touch the same file. - Does
isExcludedFromBackupsurvive an app update? It is a property of the file; set it again whenever you recreate the file.
Related questions
- 13.8.1 What does UserDefaults return for a key that is not set, how do registered defaults work, and what should and…
- 13.11.2 How do you detect a damaged database or file, and what do you do about it?
- 13.1.1 UserDefaults, Keychain, files, SQLite, Core Data or SwiftData: how do you choose, and what survives deletion…
- 13.6.1 How do you protect data from crashes mid-write, corruption and backups: atomic writes, journaling, exclusion…
← What does UserDefaults return for a key that is not set, how do…