How do you read and write files safely, and why is fileExists-then-act a bug?

2.3.7 · L2 · Interview

Predict first

What does this print?

do {
    try FileManager.default.removeItem(at: URL(fileURLWithPath: "/tmp/definitely-missing-file"))
} catch {
    print((error as? CocoaError)?.code == .fileNoSuchFile)
}
  • true
  • false
  • Nothing

Predict first

What does this print?

let dir = FileManager.default.temporaryDirectory.appending(path: "p-\(UUID())/a/b")
do {
    try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: false)
    print("created")
} catch {
    print("threw")
}
  • created
  • threw

Mental model. Atomic writing is preparing the new page on a separate sheet and then swapping it in, so the book is never torn open halfway.

Write with data.write(to:options: .atomic): it writes a temporary file and renames it into place, so a crash never leaves a half-written file. Read small files with Data(contentsOf:), big ones with .mappedIfSafe or a FileHandle that reads chunks. Failures arrive as errors, typically CocoaError with a code like .fileNoSuchFile.

Do not check fileExists and then act: the file can change in between (a time-of-check/time-of-use race). Just try the operation and handle the error. createDirectory(withIntermediateDirectories: true) succeeds if the folder already exists and builds missing parents; with false it throws if a parent is missing. For iCloud or shared files use NSFileCoordinator.

Removing something that is not there throws a CocoaError with .fileNoSuchFile. It does not silently succeed.

With withIntermediateDirectories: false, a missing parent is an error. Pass true to build the whole chain.

Common traps

  • fileExists then removeItem. Two steps, one race. Call removeItem and catch .fileNoSuchFile.
  • Reading a huge file into memory at once. Map it or stream it.

They might follow up with

  • Does .atomic protect against concurrent writers? No. It protects against torn writes, not two writers overwriting each other.
  • How do you protect a file at rest? Pass a data protection option such as .completeFileProtection.

Related questions

  • 2.3.5 What is Data, and how do you convert it to and from text?
  • 2.3.6 Where should an iOS app put files: Documents, Application Support, Caches or tmp?

← Where should an iOS app put files: Documents, Application Support…