Добавление поддержки Codable в UserDefaults с помощью Swift

Если вы разработчик iOS, скорее всего, вы уже использовали UserDefaults в прошлом для сохранения и загрузки локальных данных.

В настоящее время UserDefaults поддерживает следующие типы данных:

- URL
- Any (NSData, NSString, NSNumber, NSDate, NSArray, or NSDictionary)
- Bool
- Double
- Float
- Int
- String

Хорошо, но как насчет Codable? 🤔

Допустим, у нас есть структура Starship, соответствующая Codable.

struct Starship: Codable {
    let name: String
    let captain: String
}
let enterprise = Starship(name: "Enterprise D", captain: "Jean Luc Picard")

Как мы можем сохранить константу enterprise в UserDefaults?

На данный момент единственным способом является преобразование корпоративных данных в данные перед их сохранением. А позже, при получении значения, нам нужно преобразовать Data в Starship.

Довольно скучно, верно? 😴

Разве не было бы полезно иметь пару методов для сохранения и загрузки кодируемого значения?

Улучшение пользовательских значений по умолчанию

Хорошо, давайте добавим в наш проект следующее расширение UserDefault.

extension UserDefaults {
func set<Element: Codable>(value: Element, forKey key: String) {
        let data = try? JSONEncoder().encode(value)
        UserDefaults.standard.setValue(data, forKey: key)
    }
func codable<Element: Codable>(forKey key: String) -> Element? {
        guard let data = UserDefaults.standard.data(forKey: key) else { return nil }
        let element = try? JSONDecoder().decode(Element.self, from: data)
        return element
    }
}
  • Первый метод получает кодируемое значение и сохраняет его в UserDefaults.
  • Второй метод возвращает значение Codable, связанное с данным ключом.

Благодаря этим методам теперь мы можем использовать UserDefaults с любым кодируемым типом.

Тест 🔨

Наконец, давайте проведем тест.

struct Starship: Codable {
    let name: String
    let captain: String
}
let enterprise = Starship(name: "Enterprise D", captain: "Jean Luc Picard")
UserDefaults.standard.set(value: enterprise, forKey: "enterprise")
let value: Starship? = UserDefaults.standard.codable(forKey: "enterprise")

Теперь мы можем напечатать значениеконстанту.

Optional(Starship(name: "Enterprise D", captain: "Jean Luc Picard"))

Зачем нам нужно объявлять тип значения, которое мы получаем? 🤔

Хороший вопрос, зачем нам это писать

let value: Starship? = UserDefaults.standard.codable(forKey: "enterprise")

вместо этого?

let value = UserDefaults.standard.codable(forKey: "enterprise")

В отличие от других методов UserDefaults (например, UserDerfaults.standard.integer(forKey:)), наш метод codable(forKey:) не всегда возвращает один и тот же тип.

Смотри сюда 👇

struct Starship: Codable {
    let name: String
    let captain: String
}
struct SpaceStation: Codable {
    let name: String
}
let enterprise = Starship(name: "Enterprise D", captain: "Jean Luc Picard")
let deepSpace9 = SpaceStation(name: "Deep Space Nine")
UserDefaults.standard.set(value: enterprise, forKey: "enterprise")
UserDefaults.standard.set(value: deepSpace9, forKey: "deepSpace9")
let value0: Starship? = UserDefaults.standard.codable(forKey: "enterprise")
let value1: SpaceStation? = UserDefaults.standard.codable(forKey: "deepSpace9")

Обратите внимание на последние две строки: наш метод возвращает Starship, а затем SpaceStation.

Вот почему нам нужно сообщить нашему методу тип, который мы ожидаем.