What do the file protection classes mean, how do you set them, and why can you not test them in the simulator?
Predict first
What does the simulator report for the protection class?
import Foundation
let dir = FileManager.default.temporaryDirectory.appendingPathComponent("fp\(getpid())")
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
let f = dir.appendingPathComponent("a.bin")
try Data("x".utf8).write(to: f, options: .completeFileProtection)
print("class after write:", try FileManager.default.attributesOfItem(atPath: f.path)[.protectionKey] as Any)
try FileManager.default.setAttributes([.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication], ofItemAtPath: f.path)
print("class after setAttributes:", try FileManager.default.attributesOfItem(atPath: f.path)[.protectionKey] as Any)
print(FileProtectionType.complete.rawValue)
print(FileProtectionType.completeUnlessOpen.rawValue)
print(FileProtectionType.completeUntilFirstUserAuthentication.rawValue)
print(FileProtectionType.none.rawValue)
try? FileManager.default.removeItem(at: dir)
- class after write: nil / class after setAttributes: nil / NSFileProtectionComplete / NSFileProtectionCompleteUnlessOpen / NSFileProtectionCompleteUntilFirstUserAuthentication / NSFileProtectionNone
- class after write: Optional(NSFileProtectionComplete) / class after setAttributes: Optional(NSFileProtectionCompleteUntilFirstUserAuthentication)
- class after write: nil / class after setAttributes: Optional(NSFileProtectionNone)
Mental model. Each file is a document in a safe with a combination tied to the phone’s lock. The class says when the safe is allowed to stay open, and the simulator is a prop safe with no lock.
Data Protection ties a file’s encryption key to the device passcode. The four classes, with the raw strings you see in code: NSFileProtectionComplete (readable only while the device is unlocked), CompleteUnlessOpen (a file opened while unlocked can still be written after locking; for downloads and recordings), CompleteUntilFirstUserAuthentication (readable after the first unlock since boot until the next restart; this is the default for most app data, and the right choice for data a background task needs) and None (no protection beyond device encryption). Choose per file with the write option (.completeFileProtection and the other options) or later with setAttributes([.protectionKey: …]); the Core Data/SwiftData store and Keychain have their own equivalent (the Keychain’s kSecAttrAccessible… classes, Q329). The trade-off is background access: a Complete file cannot be read by a background fetch or a push-triggered task while the phone is locked, and the failure looks like a permission error that only happens on locked devices.
The behaviour is easy to mis-test. The same program run on macOS reports NSFileProtectionComplete after the write and the changed class after setAttributes, but run in the iOS simulator attributesOfItem returns nil for the protection key both times: the simulator does not implement Data Protection, so a unit test that asserts the class passes or fails for reasons unrelated to a device. Verify on a real device (including with the device locked and a background task reading the file), set a default class in the target’s Data Protection capability or entitlement (com.apple.developer.default-data-protection), and decide per file. Also remember protection is about passcode-locked access, not about other apps: the sandbox does that. Not run: any real device behaviour (lock state, background reads).
The simulator does not store or report Data Protection classes; the constants are still defined.
Common traps
- Marking a file
Completethat a background task must read. It fails only while the device is locked. - Asserting protection classes in simulator tests. The simulator returns no class.
- Assuming file protection hides data from your own app’s other code. It only guards the locked-device case.
They might follow up with
- What is the default class? Normally complete-until-first-user-authentication.
- How do you set a default for new files? The default Data Protection entitlement, then override per file.
Related questions
- 14.7.4 How does a Secure Enclave key behave in code: what is stored, what can be restored, and what can you not do…
- 14.8.1 What is NSSecureCoding, what attack does it prevent, and how do you unarchive data from an untrusted source?
- 13.1.2 How does the Keychain work, how do you add, read and update an item, and what do accessibility classes and…
- 14.3.3 How do you keep personal data out of logs, screenshots, the pasteboard and the app switcher?
← How do you keep third-party code from being swapped under you…