在 UserDefaults 中存储自定义对象数组

Posted

技术标签:

【中文标题】在 UserDefaults 中存储自定义对象数组【英文标题】:Storing array of custom objects in UserDefaults 【发布时间】:2020-08-12 00:03:35 【问题描述】:

我花了很长时间试图弄清楚如何在 UserDefaults 中存储我的自定义结构的数组。

这是我的代码:

struct DomainSchema: Codable 
    var domain: String
    var schema: String


var domainSchemas: [DomainSchema] 
    get 
        if UserDefaults.standard.object(forKey: "domainSchemas") != nil 
            let data = UserDefaults.standard.value(forKey: "domainSchemas") as! Data
            let domainSchema = try? PropertyListDecoder().decode(DomainSchema.self, from: data)
            
            return domainSchema!
        
        
        return nil
    
    
    set 
        UserDefaults.standard.set(try? PropertyListEncoder().encode(newValue), forKey: "domainSchemas")
    


struct SettingsView: View 
    var body: some View 
        VStack 
            ForEach(domainSchemas, id: \.domain)  domainSchema in
                HStack 
                    Text(domainSchema.domain)
                    Text(domainSchema.schema)
                
            
            
            // clear history button
        
        .onAppear 
            if (domainSchemas.isEmpty) 
                domainSchemas.append(DomainSchema(domain: "reddit.com", schema: "apollo://"))
            
        
    

它给了我这些错误:

无法将类型“DomainSchema”的返回表达式转换为返回类型“[DomainSchema]”

“nil”与返回类型“[DomainSchema]”不兼容

我不确定如何获取对象数组而不是单个对象,或者如何解决 nil 不兼容错误...

【问题讨论】:

UserDefaults 并不意味着存储您的应用数据。只需使用 JSONEncoder 对您的集合进行编码并将结果数据写入磁盘。您可以将其保存到您的应用程序支持目录。 注意不要使用value(forKey:)方法。 UserDefaults 有一个特定的方法来检索Data,称为data(forKey:) 好吧,这是有道理的。我应该为此使用核心数据还是只写入磁盘? 如果您从未使用过Core Data,则无需使用它。这一切都取决于您的应用数据的复杂性。 我以前用过Core Data。我真的只有两组需要存储的数据。 1 是一个简单的字符串数组,另一个是我试图用DomainSchema 对象做的这个数组。 【参考方案1】:

如果您真的想使用 UserDefaults 保存数据,最简单的方法是使用一个类并使其符合 NSCoding。关于您的全局 var domainSchemas,我建议您使用单例或扩展 UserDefaults 并在那里为其创建一个计算属性:


class DomainSchema: NSObject, NSCoding 
    var domain: String
    var schema: String
    init(domain: String, schema: String) 
        self.domain = domain
        self.schema = schema
    
    required init(coder decoder: NSCoder) 
        self.domain = decoder.decodeObject(forKey: "domain") as? String ?? ""
        self.schema = decoder.decodeObject(forKey: "schema") as? String ?? ""
    
    func encode(with coder: NSCoder) 
        coder.encode(domain, forKey: "domain")
        coder.encode(schema, forKey: "schema")
    


extension UserDefaults 
    var domainSchemas: [DomainSchema] 
        get 
            guard let data = UserDefaults.standard.data(forKey: "domainSchemas") else  return [] 
            return (try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)) as? [DomainSchema] ?? []
        
        set 
            UserDefaults.standard.set(try? NSKeyedArchiver.archivedData(withRootObject: newValue, requiringSecureCoding: false), forKey: "domainSchemas")
        
    


用法:

UserDefaults.standard.domainSchemas = [.init(domain: "a", schema: "b"), .init(domain: "c", schema: "d")]

UserDefaults.standard.domainSchemas  // [NSObject, domain "a", schema "b", NSObject, domain "c", schema "d"]


如果您更喜欢使用 UserDefaults 持久化数据的 Codable 方法:


struct DomainSchema: Codable 
    var domain: String
    var schema: String
    init(domain: String, schema: String) 
        self.domain = domain
        self.schema = schema
    


extension UserDefaults 
    var domainSchemas: [DomainSchema] 
        get 
            guard let data = UserDefaults.standard.data(forKey: "domainSchemas") else  return [] 
            return (try? PropertyListDecoder().decode([DomainSchema].self, from: data)) ?? []
        
        set 
            UserDefaults.standard.set(try? PropertyListEncoder().encode(newValue), forKey: "domainSchemas")
        
    


用法:

UserDefaults.standard.domainSchemas = [.init(domain: "a", schema: "b"), .init(domain: "c", schema: "d")]

UserDefaults.standard.domainSchemas  // [domain "a", schema "b", domain "c", schema "d"]

我认为最好的选择是不使用 UserDefaults,创建一个单例“共享实例”,在那里声明一个 domainSchemas 属性并将您的 json 数据保存在应用程序支持目录的子目录中:

extension URL 
    static var domainSchemas: URL 
        let applicationSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!
        let bundleID = Bundle.main.bundleIdentifier ?? "company name"
        let subDirectory = applicationSupport.appendingPathComponent(bundleID, isDirectory: true)
        try? FileManager.default.createDirectory(at: subDirectory, withIntermediateDirectories: true, attributes: nil)
        return subDirectory.appendingPathComponent("domainSchemas.json")
    


class Shared 
    static let instance = Shared()
    private init()  
    var domainSchemas: [DomainSchema] 
        get 
            guard let data = try? Data(contentsOf: .domainSchemas) else  return [] 
            return (try? JSONDecoder().decode([DomainSchema].self, from: data)) ?? []
        
        set 
            try? JSONEncoder().encode(newValue).write(to: .domainSchemas)
        
    


用法:

Shared.instance.domainSchemas = [.init(domain: "a", schema: "b"), .init(domain: "c", schema: "d")]

Shared.instance.domainSchemas  // [domain "a", schema "b", domain "c", schema "d"]

【讨论】:

【参考方案2】:

您不需要使用 NSKeyedArchiver 将自定义对象保存到 UserDefaults 因为您必须将结构更改为类。 有一个更简单的解决方案,那就是 JSONDecoderJSONEncoder。 每当您想将自定义对象保存到 UserDefaults 中时,首先使用 JSONEncoder 将其转换为数据,当您想从 Userdefaults 中检索对象时,您可以使用 JSONDecoder 来完成。除此之外,我强烈建议您编写一个单独的类或结构来管理您的数据,这样就可以做到:

 struct DomainSchema: Codable 
        var domain: String
        var schema: String
 

 struct PersistenceMangaer

        static let defaults = UserDefaults.standard
        private init()
        
        // save Data method
        static func saveDomainSchema(domainSchema: [DomainSchema])
          do
             let encoder = JSONEncoder()
             let domainsSchema = try encoder.encode(domainSchema)
             defaults.setValue(followers, forKey: "yourKeyName")
          catch let err
             print(err)
          
       
       
      //retrieve data method
      static func getDomains() -> [DomainSchema]
    
             guard let domainSchemaData = defaults.object(forKey: "yourKeyName") as? Data elsereturn
             do
                 let decoder = JSONDecoder()
                 let domainsSchema = try decoder.decode([DomainSchema].self, from: domainSchemaData)
                 return domainsSchema
             catch let err
                 return([])
           
        
 

用法

let domains = PersistenceMangaer.standard.getDomains()
PersistenceMangaer.standard.saveDomainSchema(domainsTosave) 

【讨论】:

以上是关于在 UserDefaults 中存储自定义对象数组的主要内容,如果未能解决你的问题,请参考以下文章

UserDefaults/KeyedArchiver 挫折

UserDefaults 自定义对象的自定义对象

尝试使用 NSKeyedArchiver 在 UserDefaults 中保存自定义对象

将自定义对象存储到 NSMutableArray,然后将此 NSMutableArray 存储到 NSUserDefaults

如何在 swift 4 中的 UserDefalts 中设置自定义类数组数据

Swift 3.0:在 TableView 的 UserDefaults 中存储数组