Чтобы очистить значение из UserDefaults, я раньше думал, что можно сделать что-то вроде этого:

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

Оказывается, это работает по-разному в разных версиях iOS. Начиная с iOS 11, установка nil для ключа работает, как я и ожидал, и следующее печатает nil:

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

Однако в iOS 10 UserDefaults фактически пытается сериализовать nil в Data:

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

Если мы посмотрим на эти данные, может показаться, что это 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[")

Оказывается, мы можем использовать PropertyListSerialization, чтобы распечатать сериализованное значение nil:

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

По какой-то причине это было совершенно неожиданно для меня. Это особенно сложно, когда вы пытаетесь интерпретировать значение ранее очищенного ключа UserDefaults как 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")
}

Это будет отлично работать на iOS 11+, но выдаст ошибку на iOS 10.

Вот пример проекта, который демонстрирует такое поведение. В итоге я нашел об этом один пост stackoverflow, но, насколько я могу судить, это нигде не задокументировано. Если у вас есть дополнительная информация об этом, дайте мне знать 👋

Усвоенный урок — установка nil — не лучший способ очистить значение UserDefaults. Вместо этого просто используйте рекомендуемый подход:

UserDefaults.standard.removeObject(forKey: "myKey")