How NOT to set nil in UserDefaults

October 22, 2018

To clear a value from UserDefaults, I previously thought it was fine to do something like this:

UserDefaults.standard.set(nil, forKey: "myKey")

Turns out that this works differently in different iOS versions. Starting in iOS 11, setting nil for a key works as I expected and the following prints nil:

UserDefaults.standard.set(nil, forKey: "myKey")
print(UserDefaults.standard.data(forKey: "myKey"))
// nil

However, in iOS 10, UserDefaults actually tries to serialize nil into a Data:

UserDefaults.standard.set(nil, forKey: "myKey")
print(UserDefaults.standard.data(forKey: "myKey"))
// 135 bytes

If we look at this data, it seems like it might be a plist:

print(String(
    data: UserDefaults.standard.data(forKey: "myKey")!,
    encoding: .ascii
))
Optional("bplist00Ô\u{01}\u{02}\u{03}\u{04}\u{05}\u{08}\n\u{0B}T$topX$objectsX$versionY$archiverÑ\u{06}\u{07}Troot€\0¡\tU$null\u{12}\0\u{01}† _\u{10}\u{0F}NSKeyedArchiver\u{08}\u{11}\u{16}\u{1F}(25:<>DI\0\0\0\0\0\0\u{01}\u{01}\0\0\0\0\0\0\0\u{0C}\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0[")

Turns out that we can use PropertyListSerialization to actually print the serialized value of nil:

let data = UserDefaults.standard.data(forKey: "myKey")!
let propertyList = try! PropertyListSerialization.propertyList(
   from: data,
   options: [],
   format: nil
)
print(propertyList)
{
    "$archiver" = NSKeyedArchiver;
    "$objects" =     (
        "$null"
    );
    "$top" =     {
        root = "<CFKeyedArchiverUID 0x79e7c420 [0xf126e8]>{value = 0}";
    };
    "$version" = 100000;
}

For whatever reason, this was totally unexpected to me. It’s particularly tricky when you’re trying to interpret the value of a previously cleared UserDefaults key as JSON:

if let data = UserDefaults.standard.data(forKey: "myKey") {
    let json = try JSONDecoder().decode(MyClass.self, from: data)
    print(json)
} else {
    print("No data found")
}

This will work just fine on iOS 11+, but throw an error on iOS 10.

Here’s an example project which demonstrates this behavior. I ended up finding one stackoverflow post about it, but as far as I can tell this isn’t documented anywhere. If you have any more info about it, let me know 👋

Lesson learned — setting nil isn’t a good way to clear a UserDefaults value. Instead, use the recommended approach:

UserDefaults.standard.removeObject(forKey: "myKey")
Picture of me with a corgi

Noah Gilmore

Hello! I'm Noah, a software developer based in the San Francisco bay area. I focus mainly on iOS, Apple platform development, and full stack web development.

  • 💻 I'm writing a macOS editor for Atlassian Confluence called Fluency
  • 📱 I wrote an app which lets you create transparent app icons called Transparent App Icons
  • 🧩 I made a puzzle game for iPhone and iPad called Trestle
  • 🎨 I wrote a CoreImage filter utility app for iOS developers called CIFilter.io
  • 👋 Please feel free to reach out on Twitter