尝试在 Swift 3 中保存自定义对象时尝试插入非属性列表对象

Posted

技术标签:

【中文标题】尝试在 Swift 3 中保存自定义对象时尝试插入非属性列表对象【英文标题】:Attempt to insert non-property list object when trying to save a custom object in Swift 3 【发布时间】:2022-01-09 07:08:16 【问题描述】:

我有一个符合NSCoding 协议的简单对象。

import Foundation

class JobCategory: NSObject, NSCoding 
    var id: Int
    var name: String
    var URLString: String

    init(id: Int, name: String, URLString: String) 
        self.id = id
        self.name = name
        self.URLString = URLString
    

    // MARK: - NSCoding
    required init(coder aDecoder: NSCoder) 
        id = aDecoder.decodeObject(forKey: "id") as? Int ?? aDecoder.decodeInteger(forKey: "id")
        name = aDecoder.decodeObject(forKey: "name") as! String
        URLString = aDecoder.decodeObject(forKey: "URLString") as! String
    

    func encode(with aCoder: NSCoder) 
        aCoder.encode(id, forKey: "id")
        aCoder.encode(name, forKey: "name")
        aCoder.encode(URLString, forKey: "URLString")
    

我正在尝试将它的一个实例保存在 UserDefaults 中,但它一直失败并出现以下错误。

由于未捕获的异常“NSInvalidArgumentException”而终止应用程序,原因:“尝试为关键 jobCategory 插入非属性列表对象”

这是我保存在UserDefaults 中的代码。

enum UserDefaultsKeys: String 
    case jobCategory


class ViewController: UIViewController 

    @IBAction func didTapSaveButton(_ sender: UIButton) 
        let category = JobCategory(id: 1, name: "Test Category", URLString: "http://www.example-job.com")

        let userDefaults = UserDefaults.standard
        userDefaults.set(category, forKey: UserDefaultsKeys.jobCategory.rawValue)
        userDefaults.synchronize()
    

我将枚举值替换为普通字符串,但仍然出现相同的错误。知道是什么原因造成的吗?

【问题讨论】:

我创建了一个类似的问题,询问这是在哪里记录的(在线或在操作指南中)。你找到这方面的资源了吗? userDefaults.synchronize 是不必要的,不应使用。 【参考方案1】:

您需要使用archivedData(withRootObject:) 从您的JobCategory 实例创建Data 实例,并将该Data 实例存储在UserDefaults 中,然后使用unarchiveTopLevelObjectWithData(_:) 取消归档,所以试试这样。

用于将数据存储在UserDefaults

let category = JobCategory(id: 1, name: "Test Category", URLString: "http://www.example-job.com")
let encodedData = NSKeyedArchiver.archivedData(withRootObject: category, requiringSecureCoding: false)
let userDefaults = UserDefaults.standard
userDefaults.set(encodedData, forKey: UserDefaultsKeys.jobCategory.rawValue)

用于从UserDefaults检索数据

let decoded  = UserDefaults.standard.object(forKey: UserDefaultsKeys.jobCategory.rawValue) as! Data
let decodedTeams = NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decoded) as! JobCategory
print(decodedTeams.name)

【讨论】:

我正在尝试在 NSArchiveObject 上执行此操作,但会导致错误 我创建了一个类似的问题,询问这是在哪里记录的(在线或在操作指南中)。你找到这方面的资源了吗? NSKeyedArchiver 可能会引发错误,因此应将其标记为“尝试”并同时处理错误。 我正在尝试存储方法,我收到以下错误:数据无法写入,因为它的格式不正确。【参考方案2】:

更新 Swift 4、Xcode 10

我已经围绕它编写了一个结构以便于访问。

//set, get & remove User own profile in cache
struct UserProfileCache 
    static let key = "userProfileCache"
    static func save(_ value: Profile!) 
         UserDefaults.standard.set(try? PropertyListEncoder().encode(value), forKey: key)
    
    static func get() -> Profile! 
        var userData: Profile!
        if let data = UserDefaults.standard.value(forKey: key) as? Data 
            userData = try? PropertyListDecoder().decode(Profile.self, from: data)
            return userData!
         else 
            return userData
        
    
    static func remove() 
        UserDefaults.standard.removeObject(forKey: key)
    

Profile 是一个 Json 编码的对象。

struct Profile: Codable 
let id: Int!
let firstName: String
let dob: String!

用法:

//save details in user defaults...
UserProfileCache.save(profileDetails)

希望对你有帮助!!!

谢谢

【讨论】:

【参考方案3】:

将字典保存到用户默认中

let data = NSKeyedArchiver.archivedData(withRootObject: DictionaryData)
UserDefaults.standard.set(data, forKey: kUserData)

检索字典

let outData = UserDefaults.standard.data(forKey: kUserData)
let dict = NSKeyedUnarchiver.unarchiveObject(with: outData!) as! NSDictionary

【讨论】:

我创建了一个类似的问题,询问这是在哪里记录的(在线或在操作指南中)。你找到这方面的资源了吗?【参考方案4】:

SwiftCodable 对象保存到 UserDefault@propertyWrapper

@propertyWrapper
    struct UserDefault<T: Codable> 
        let key: String
        let defaultValue: T

        init(_ key: String, defaultValue: T) 
            self.key = key
            self.defaultValue = defaultValue
        

        var wrappedValue: T 
            get 

                if let data = UserDefaults.standard.object(forKey: key) as? Data,
                    let user = try? JSONDecoder().decode(T.self, from: data) 
                    return user

                

                return  defaultValue
            
            set 
                if let encoded = try? JSONEncoder().encode(newValue) 
                    UserDefaults.standard.set(encoded, forKey: key)
                
            
        
    




enum GlobalSettings 

    @UserDefault("user", defaultValue: User(name:"",pass:"")) static var user: User

示例用户模型确认可编码

struct User:Codable 
    let name:String
    let pass:String

如何使用

//Set value 
 GlobalSettings.user = User(name: "Ahmed", pass: "Ahmed")

//GetValue
print(GlobalSettings.user)

【讨论】:

这看起来像一些巫毒魔法✨但我喜欢它! 有人在玩属性包装器,哈哈。干杯。【参考方案5】:

基于Harjot Singh 的回答。我是这样用的:

struct AppData 

    static var myObject: MyObject? 

        get 
            if UserDefaults.standard.object(forKey: "UserLocationKey") != nil 
                if let data = UserDefaults.standard.value(forKey: "UserLocationKey") as? Data 
                    let myObject = try? PropertyListDecoder().decode(MyObject.self, from: data)
                    return myObject!
                
            
            return nil
        

        set 
            UserDefaults.standard.set(try? PropertyListEncoder().encode(newValue), forKey: "UserLocationKey")
        

    

【讨论】:

【参考方案6】:

这是一个 UserDefaults 扩展,用于设置和获取 Codable 对象,如果您将其作为纯文本文件打开,则在 plist(用户默认值)中保持人类可读:

extension Encodable 
    var asDictionary: [String: Any]? 
        guard let data = try? JSONEncoder().encode(self) else  return nil 
        return try? JSONSerialization.jsonObject(with: data) as? [String : Any]
    


extension Decodable 
    init?(dictionary: [String: Any]) 
        guard let data = try? JSONSerialization.data(withJSONObject: dictionary) else  return nil 
        guard let object = try? JSONDecoder().decode(Self.self, from: data) else  return nil 
        self = object
    


extension UserDefaults 
    func setEncodableAsDictionary<T: Encodable>(_ encodable: T, for key: String) 
        self.set(encodable.asDictionary, forKey: key)
    

    func getDecodableFromDictionary<T: Decodable>(for key: String) -> T? 
        guard let dictionary = self.dictionary(forKey: key) else 
            return nil
        
        return T(dictionary: dictionary)
    

如果您还想支持(可编码的)数组与 plist 数组之间的往来,请将以下内容添加到扩展中:

extension UserDefaults 
    func setEncodablesAsArrayOfDictionaries<T: Encodable>(_ encodables: Array<T>, for key: String) 
        let arrayOfDictionaries = encodables.map( $0.asDictionary )
        self.set(arrayOfDictionaries, forKey: key)
    

    func getDecodablesFromArrayOfDictionaries<T: Decodable>(for key: String) -> [T]? 
        guard let arrayOfDictionaries = self.array(forKey: key) as? [[String: Any]] else 
            return nil
        
        return arrayOfDictionaries.compactMap( T(dictionary: $0) )
    


如果您不关心 plist 是否可读,可以简单地将其保存为 Data(如果以纯文本形式打开,则看起来像随机字符串):

extension UserDefaults 
    func setEncodable<T: Encodable>(_ encodable: T, for key: String) throws 
        let data = try PropertyListEncoder().encode(encodable)
        self.set(data, forKey: key)
    

    func getDecodable<T: Decodable>(for key: String) -> T? 
        guard
            self.object(forKey: key) != nil,
            let data = self.value(forKey: key) as? Data
        else 
            return nil
        

        let obj = try? PropertyListDecoder().decode(T.self, from: data)
        return obj
    

(使用第二种方法,您不需要顶部的 EncodableDecodable 扩展)

【讨论】:

以上是关于尝试在 Swift 3 中保存自定义对象时尝试插入非属性列表对象的主要内容,如果未能解决你的问题,请参考以下文章

存储自定义对象数组 Swift

如何在Swift 3中修复我的自定义UITextField对象?

目标 C:我正在尝试保存一个包含原语和对象的 NSDictionary,但是在我尝试添加自定义对象时发生错误

在 Swift 中使用协议作为类型时出错

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

如何在 Swift 中归档和取消归档自定义对象?或者如何在 Swift 中将自定义对象保存到 NSUserDefaults?