Как кодировать / декодировать словарь со значениями Codable для хранения в UserDefaults? - PullRequest
0 голосов
/ 26 июня 2019

Я пытаюсь сохранить словарь названий компаний (строки), сопоставленных с объектами компании (из структуры Company) в iOS UserDefaults.Я создал структуру компании и привел ее в соответствие с Codable.У меня есть один пример, с которым мне помог друг в моем проекте, где мы создали класс Account и сохранили его в UserDefaults, создав структуру Defaults (будет включать пример кода).В быстрых документах я прочитал, что словари соответствуют Codable и, чтобы оставаться Codable, должны содержать Codable объекты.Вот почему я сделал struct Company соответствующей Codable.

Я создал структуру для Company, которая соответствует Codable.Я попытался использовать код модели для создания новой структуры CompanyDefaults для обработки получения и настройки словаря компании из / в UserDefaults.Я чувствую, что у меня есть некоторые неправильные представления новичка о том, что должно произойти и о том, как это должно быть реализовано (с учетом хорошего дизайна).

Словарь, который я хочу сохранить, выглядит как [String:Company], где название компании будет Stringи объект Company для Company

, который я использовал, соответствовал Codable, поскольку я проводил некоторые исследования, и он казался более новым методом для выполнения аналогичных задач.

Структура компании

struct Company: Codable {
    var name:String?
    var initials:String? = nil
    var logoURL:URL? = nil
    var brandColor:String? = nil // Change to UIColor

    enum CodingKeys:String, CodingKey {
        case name = "name"
        case initials = "initials"
        case logoURL = "logoURL"
        case brandColor = "brandColor"
    }

    init(name:String?, initials:String?, logoURL:URL?, brandColor:String?) {
        self.name = name
        self.initials = initials
        self.logoURL = logoURL
        self.brandColor = brandColor
    }

    init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        name = try values.decode(String.self, forKey: .name)
        initials = try values.decode(String.self, forKey: .initials)
        logoURL = try values.decode(URL.self, forKey: .logoURL)
        brandColor = try values.decode(String.self, forKey:   .brandColor)

    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(initials, forKey: .initials)
        try container.encode(logoURL, forKey: .logoURL)
        try container.encode(brandColor, forKey: .brandColor)
    }

}

Структура по умолчанию для управления хранилищем

struct CompanyDefaults {

    static private let companiesKey = "companiesKey"

    static var companies: [String:Company] = {

        guard let data = UserDefaults.standard.data(forKey: companiesKey) else { return [:] }
        let companies = try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? [String : Company] ?? [:]
        return companies!
        }() {
        didSet {
            guard let data = try? NSKeyedArchiver.archivedData(withRootObject: companies, requiringSecureCoding: false) else {
                return
            }

            UserDefaults.standard.set(data, forKey: companiesKey)
        }
    }
}

Я должен иметь возможность ссылаться на сохраненный словарь во всем моем коде, например CompanyDefaults.companies.count

Для справки, друг помог мне выполнить аналогичную задачу длямассив классов Account хранится в пользовательских настройках по умолчанию.Код, который идеально подходит для этого ниже.Причина, по которой я попробовал другой способ, состоит в том, что у меня была другая структура данных (словарь) и я принял решение использовать структуры.

class Account: NSObject, NSCoding {

    let service: String
    var username: String
    var password: String

    func encode(with aCoder: NSCoder) {
        aCoder.encode(service)
        aCoder.encode(username)
        aCoder.encode(password)
    }

    required init?(coder aDecoder: NSCoder) {
        guard let service = aDecoder.decodeObject() as? String,
        var username = aDecoder.decodeObject() as? String,
        var password = aDecoder.decodeObject() as? String else {
            return nil
        }

        self.service = service
        self.username = username
        self.password = password

    }

    init(service: String, username: String, password: String) {
        self.service = service
        self.username = username
        self.password = password
    }

}
struct Defaults {

    static private let accountsKey = "accountsKey"

    static var accounts: [Account] = {

        guard let data = UserDefaults.standard.data(forKey: accountsKey) else { return [] }

        let accounts = NSKeyedUnarchiver.unarchiveObject(with: data) as? [Account] ?? []
        return accounts

        }() {
        didSet {
            guard let data = try? NSKeyedArchiver.archivedData(withRootObject: accounts, requiringSecureCoding: false) else {
                return
            }
            UserDefaults.standard.set(data, forKey: accountsKey)
        }
    }

}

1 Ответ

0 голосов
/ 26 июня 2019

Вы смешиваете NSCoding и Codable. Для первого требуется под класс из NSObject, последний может кодировать структуры и классы напрямую с JSONEncoder или ProperListEncoder без Keyedarchiver, который также принадлежит NSCoding.

Ваша структура может быть уменьшена до

struct Company: Codable {
    var name : String
    var initials : String
    var logoURL : URL?
    var brandColor : String?
}

Вот и все, CodingKeys и другие методы синтезированы. По крайней мере, я бы объявил name и initials необязательными.

Прочитать и сохранить данные довольно просто. Соответствующая CompanyDefaults структура:

struct CompanyDefaults {

    static private let companiesKey = "companiesKey"

    static var companies: [String:Company] = {

        guard let data = UserDefaults.standard.data(forKey: companiesKey) else { return [:] }
        return try? JSONDecoder.decode([String:Company].self, from: data) ?? [:]
        }() {
        didSet {
            guard let data = try? JSONEncoder().encode(companies) else { return }
            UserDefaults.standard.set(data, forKey: companiesKey)
        }
    }
}
...