【问题标题】:How to encode/decode a dictionary with Codable values for storage in UserDefaults?如何使用可编码值对字典进行编码/解码以存储在 UserDefaults 中?
【发布时间】:2019-06-26 04:49:09
【问题描述】:

我正在尝试在 iOS UserDefaults 中存储映射到公司对象(来自结构公司)的公司名称(字符串)字典。我创建了 Company 结构并使其符合 Codable。我有一个例子,一位朋友在我的项目中帮助我创建了一个类 Account,并通过创建一个 Defaults 结构(将包括示例代码)将其存储在 UserDefaults 中。我在 swift 文档中读到字典符合 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)
        }
    }

}

【问题讨论】:

    标签: nsuserdefaults codable swift5 swift-dictionary nsarchiving


    【解决方案1】:

    您混淆了NSCodingCodable。前者需要NSObject 的子,后者可以直接用JSONEncoderProperListEncoder 编码结构和类,而无需任何Keyedarchiver,也属于NSCoding

    你的结构可以简化为

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

    就是这样,CodingKeys 和其他方法合成了。我至少会声明 nameinitials 是非可选的。

    读取和保存数据非常简单。对应的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)
            }
        }
    }
    

    【讨论】:

    • 太棒了,这完美!我试图对 Codable 和 NSCoding 进行尽职调查,但我显然对此表示怀疑。为什么 NSCoding 需要子类,而 Codable 更灵活(允许结构或类实现,对吗?)?更重要的是,为什么我们不必在字典中手动编码Company 类型? Codable 是否在幕后对字典内容的类型进行编码,并对整个对象进行编码?
    • Codable 仅适用于 Swift。默认情况下,协议扩展提供了很多(合成的)样板代码。 NSCoding 在 Objective-C 运行时中存在很长时间,这就是为什么需要 NSObject 子类的原因。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-27
    • 2018-05-14
    • 1970-01-01
    • 2010-10-26
    • 1970-01-01
    • 1970-01-01
    • 2012-01-08
    相关资源
    最近更新 更多