【问题标题】:Storing/retriving a Codable Dictionary of structs in UserDefaults doesn't work for me在 UserDefaults 中存储/检索结构的可编码字典对我不起作用
【发布时间】:2019-10-11 09:19:16
【问题描述】:

Swift (v 5/5.1) 新手,在 Codables 上遇到了困难...希望从这里的专家那里得到一些建议。

好的,我有一个简单的结构字典,其中键是字符串。我想将字典存储在 UserDefaults 中(然后再检索)。这里有一些非常相似的问题,但主要是解决嵌套结构的问题。

第一次尝试(为简单起见,删除了错误处理):

public struct PriceStruct:Codable {
    var myPrice: Double
    var myTime: TimeInterval
    var selected: Bool
    var direction: Int
    var myHigh, myLow: Double

    enum CodingKeys: String, CodingKey {
        case myPrice = "myPrice"
        case myTime = "myTime"
        case selected = "selected"
        case direction = "direction"
        case myHigh = "myHigh"
        case myLow = "myLow"
    }
}

var myPrices: [String: PriceStruct] = [:]

// [fill myPrices with some data...]

func savePrices() {
   // error: Attempt to set a non-property-list object
   UserDefaults.standard.set(myPrices, forKey: "prices")
}

func loadPrices() {
   // obviously this doesn't work either
   let myPrices = UserDefaults.standard.data(forKey: "prices")
}

While I assumed from the documentation, that UserDefaults is capable of storing dictionaries, it doesn't - at least for me.
Next thing I tried was using JSONEncoder like this:

    // this time with prior JSON encoding
    func savePrices() {
       // this works
       let json = try! JSONEncoder().encode(myPrices)
       UserDefaults.standard.set(json as Data, forKey: "prices")
    }

    func loadPrices() {
       // this doesn't work
       let json = UserDefaults.standard.data(forKey: "prices")
       let decoder = JSONDecoder()
       let decoded = try! decoder.decode(PriceStruct.self, from json!)
    }

不幸的是,我在尝试从 UserDefaults 加载数据时遇到错误: Swift.DecodingError.keyNotFound(CodingKeys(stringValue: "myPrice", intValue: nil), Swift.DecodingError.Context(codingPath: [], debugDescription: "No value associated with key CodingKeys(stringValue: \"myPrice\", intValue: nil) (\"myPrice\").", underlyingError: nil))

我尝试的其他变体是将编码的 JSON 转换为 UTF8 编码的字符串并存储/检索这个:

func savePrices() {
   // this works too
   let json = try! JSONEncoder().encode(myPrices)
   UserDefaults.standard.set(String(data: json, encoding: .utf8), forKey: "prices")
}

func loadPrices() {
   // and this doesn't work either
   let json = UserDefaults.standard.string(forKey: "prices")!.data(using: .utf8)

}

因此,从引发的错误来看,CodingKeys 似乎是问题的根源。我尝试使用 NSKeyedArchiver 和 NSKeyedUnarchiver` 进行切换,但没有成功。

我真的想知道是否有一个简单/通用的解决方案可以在 UserDefaults 中保存/加载字典?

感谢您的所有 cmets 和建议。谢谢!

【问题讨论】:

    标签: xcode nsuserdefaults codable swift5


    【解决方案1】:

    我尝试在我的项目中使用以下代码,这对我有用。

    用户模型

    public protocol UserModel: Codable, PrimaryKey {
    
        var id: String { get }
        var firstName: String? { get }
        var lastName: String? { get }
        var userName: String? { get }
        var emails: [String] { get }
    }
    
    public struct User: UserModel {
    
        public let id: String
        public let firstName: String?
        public let lastName: String?
        public let userName: String?
        public let emails: [String]
    
        public enum CodingKeys: String, CodingKey {
            case id = "Id"
            case firstName = "FirstName"
            case lastName = "LastName"
            case userName = "UserName"
            case emails = "Emails"
        }
    
        public init(from decoder: Decoder) throws {
            let container = try decoder.container(keyedBy: CodingKeys.self)
    
            do {
                self.id = try container.decode(String.self, forKey: .id)
                self.firstName = try container.decodeIfPresent(String.self, forKey: .firstName)
                self.lastName = try container.decodeIfPresent(String.self, forKey: .lastName)
                self.userName = try container.decodeIfPresent(String.self, forKey: .userName)
                self.emails = try container.decodeIfPresent([String].self, forKey: .emails) ?? []
            }
            catch let error {
                debugPrint(error)
                throw error
            }
        }
    }
    

    我已使用以下方式存储在 userDefault 中

    用户数据类

    class UserData: NSObject
    {
    
    let userDefaultKey = "user_information"
    
    var userData: User?
    
        func getDictionary() -> [String: Data]
        {
            var dicInfo = [String: Data]()
    
            do
            {
                let _userData = try JSONEncoder().encode(userData)
    
                dicInfo["userData_default"]  = _userData
    
            }catch let error{
                print("error while save data in User Default: \(error.localizedDescription)")
            }
    
            return dicInfo
        }
        func saveToDefault()
        {
            let userDefault = UserDefaults.standard
            userDefault.set(getDictionary(), forKey: userDefaultKey)
            userDefault.synchronize()
        }
        func loadFromDefault()
        {
            let userDefault = UserDefaults.standard
            if let dicInfo = userDefault.object(forKey: userDefaultKey) as? [String: Data]
            {
                update(dicInfo)
            }
        }
        func update(_ dictionaryInfo: [String: Data])
        {
            do
            {
                if let _userData_data = dictionaryInfo["userData_default"]
                {
                    if let _userData = try? JSONDecoder().decode(User.self, from: _userData_data) {
                        userData = _userData
                    }
                }
    
                saveToDefault()
    
            }catch let error{
                print("error while load From Default data in User Default: \(error.localizedDescription)")
            }
        }
    }
    

    希望这会对你有所帮助。

    【讨论】:

    • 虽然我很欣赏您的回答,但它并没有太大帮助。您的数据结构似乎不同。为什么要引入协议结构?谢谢。
    • @hopfi2k 实际上,我有另一个结构,其中我将所有变量声明为var 以存储用户输入值并管理 API 调用结构。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-12
    • 2020-08-09
    • 2021-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多