【问题标题】:Cannot store custom class in UserDefaults - Attempt to insert non-property list无法在 UserDefaults 中存储自定义类 - 尝试插入非属性列表
【发布时间】:2020-11-03 13:54:04
【问题描述】:

我正在尝试将自定义类存储在 UserDefaults 中,但是一旦应用启动,我就会收到 "Attempt to insert non-property list" 错误。我已经成功存储了自定义类的数组,所以我遵循了相同的原则,但是出了点问题。

这是我的课:

import Foundation

public class PlanRouteFilters: NSObject, NSCoding, Codable {
    
    var favoriteLines: Bool
    var routeType: Int
    var bus: Bool
    var train: Bool
    var subway: Bool
    var electric: Bool
    var sharedCar: Bool
    var bike: Bool
    var electricScooter: Bool
    var providers: [Provider]?
    
    override init() {
        self.favoriteLines = false
        self.routeType = 1
        self.bus = false
        self.train = false
        self.subway = false
        self.electric = false
        self.sharedCar = false
        self.bike = false
        self.electricScooter = false
        self.providers = []
    }
    
    init(favLines: Bool, rType: Int, bus: Bool, train: Bool, subway: Bool, electric: Bool, sCar: Bool, bike: Bool, eScooter: Bool, providers: [Provider]?) {
        self.favoriteLines = favLines
        self.routeType = rType
        self.bus = bus
        self.train = train
        self.subway = subway
        self.electric = electric
        self.sharedCar = sCar
        self.bike = bike
        self.electricScooter = eScooter
        self.providers = providers
        
    }
    
    //MARK: NSCoding protocol methods
    public func encode(with aCoder: NSCoder){
        aCoder.encode(self.favoriteLines, forKey: "favoriteLines")
        aCoder.encode(self.routeType, forKey: "RouteType")
        aCoder.encode(self.bus, forKey: "bus")
        aCoder.encode(self.train, forKey: "train")
        aCoder.encode(self.subway, forKey: "subway")
        aCoder.encode(self.electric, forKey: "electric")
        aCoder.encode(self.sharedCar, forKey: "sharedCar")
        aCoder.encode(self.bike, forKey: "bike")
        aCoder.encode(self.electricScooter, forKey: "electricScooter")
        aCoder.encode(self.providers, forKey: "providers")
    }
    
    public required init(coder decoder: NSCoder) {
        self.favoriteLines = decoder.decodeBool(forKey: "favoriteLines")
        self.routeType = decoder.decodeInteger(forKey: "routeType")
        self.bus = decoder.decodeBool(forKey: "bus")
        self.train = decoder.decodeBool(forKey: "train")
        self.subway = decoder.decodeBool(forKey: "subway")
        self.electric = decoder.decodeBool(forKey: "electric")
        self.sharedCar = decoder.decodeBool(forKey: "sharedCar")
        self.bike = decoder.decodeBool(forKey: "bike")
        self.electricScooter = decoder.decodeBool(forKey: "electricScooter")
        self.providers = decoder.decodeObject(forKey: "providers") as? [Provider]
    }
}

这是我设置和获取对象的方式:

var planRouteFilters: PlanRouteFilters {
        get {
            var filters = PlanRouteFilters()
            let data = self.userSettings.settings.object(forKey: "planRouteFilters") as? Data
            if nil != data {
                filters = try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data!) as! PlanRouteFilters
            }
            return filters
        }
        set {
            let data = try! NSKeyedArchiver.archivedData(withRootObject: newValue, requiringSecureCoding: false)
            self.userSettings.settings.setValue(data, forKey: "planRouteFilters")
            self.objectWillChange.send()
        }
    }

这就是我创建 userDefaults 的方式:

import Foundation

struct UserSettings {
    var settings: UserDefaults
    
    init() {
        self.settings = UserDefaults.standard
        self.settings.register(
            defaults: [
                "userCity": "",
                "showUserCityActionSheet": true,
                "showTutorial": 1,
                "initialPage": 1,
                "favoriteStops": [Place](),
                "history": [Place](), //Maximum of 10 places stored
                "notifications": [MoveMeNotification](),
                "planRouteFilters": PlanRouteFilters()
            ])
    }
}

如果我删除了应用程序启动的 PlanRouteFilters 的默认值,但这些值不会在整个应用程序中使用。这是我错的地方吗?我应该以其他方式提供默认值吗?

谢谢!

我的Provider 班级:

public class Provider: NSObject, NSCoding, Codable {

    let name: String

    init(name: String) {
      self.Name = name
    }

    public func encode(with aCoder: NSCoder) {
      aCoder.encode(self.Name, forKey: "Name")
    }

    public required init(coder decoder: NSCoder) {
      self.Name = decoder.decodeObject(forKey: "Name") as! String
    }
}

更新:

好的,我无法解决这个问题。但是,我认为我不需要在 userSettings 中设置默认值。这让我可以继续前进。但是,这是针对我的情况的。对于没有此选项的任何人,请查看 @LeoDabus 回答。

【问题讨论】:

  • Provider 应该是一个类并且也符合 NSCoding。顺便说一句,你为什么还要确认 Codable?你不需要两者。实际上 Codable 现在是一个更好的选择
  • @LeoDabus Provider符合 NSCoding。当我第一次开始开发时,使用两者是我让它工作的唯一方法。我还没来得及重构它。
  • 在下面查看我的帖子。 stackoverflow.com/q/33192675/2303865 的可能重复项
  • 已更新。感谢您的帮助。
  • 我重新安装了应用程序。没有运气。

标签: ios swift nsuserdefaults swift5


【解决方案1】:

使用 PropertyListDecoder()Codable

这是我的旧项目中的一个示例。它允许您保存对象、检索它并将其从 UserDefaults 中删除。如果您使用自定义对象作为属性,则需要将其确认为 Codable,并且在必要时可能必须使用 PropertyListDecoder()。希望这能帮助您解决最初的问题。

class User: Codable {
    var name:String?
    var username: String?
    var password: String?
    var email: String?
    

    
    func saveUserData() {
        if let encoded = try? PropertyListEncoder().encode(self) {
            UserDefaults.standard.set(, forKey: "userObject")
        }
     }

    static var currentUser:User? {
        if let userData = UserDefaults.standard.value(forKey: "userObject") as? Data, let user = try? PropertyListDecoder().decode(User.self, from: userData) {
            return user
        }
        return nil
    }

    static func logout() {
        UserDefaults.standard.removeObject(forKey: "userObject")
 

       }
}



    // To save user object
    myUserObject.saveUserData()
    
    // To retrieve 
    let user = User.currentUser
    
    // To remove 
    User.logout()

【讨论】:

  • 为我工作。谢谢! :)
【解决方案2】:

即使您的自定义类符合 NSCoding,它也不会自动工作。您需要将您的 providers 数组编码为 Data 才能对 PlanRouteFilters 进行编码:

extension NSCoder {
    func decodeData(forKey key: String) -> Data {
        decodeObject(forKey: key) as? Data ?? Data()
    }
    func decodeString(forKey key: String) -> String {
        decodeObject(forKey: key) as? String ?? ""
    }
}

public class Provider: NSObject, NSCoding {
    let name: String
    init(name: String) { self.name = name }
    public func encode(with aCoder: NSCoder) {
        aCoder.encode(name, forKey: "name")
    }
    public required init(coder decoder: NSCoder) {
        name = decoder.decodeString(forKey: "name")
    }
}

public class PlanRouteFilters: NSObject, NSCoding {
    var favoriteLines: Bool
    var routeType: Int
    var bus: Bool
    var train: Bool
    var subway: Bool
    var electric: Bool
    var sharedCar: Bool
    var bike: Bool
    var electricScooter: Bool
    fileprivate var providersData = Data()
    var providers: [Provider] = []
    override init() {
        self.favoriteLines = false
        self.routeType = 1
        self.bus = false
        self.train = false
        self.subway = false
        self.electric = false
        self.sharedCar = false
        self.bike = false
        self.electricScooter = false
        self.providers = []
    }
    init(favLines: Bool, rType: Int, bus: Bool, train: Bool, subway: Bool, electric: Bool, sCar: Bool, bike: Bool, eScooter: Bool, providers: [Provider]) {
        self.favoriteLines = favLines
        self.routeType = rType
        self.bus = bus
        self.train = train
        self.subway = subway
        self.electric = electric
        self.sharedCar = sCar
        self.bike = bike
        self.electricScooter = eScooter
        self.providers = providers
    }
    public func encode(with aCoder: NSCoder){
        aCoder.encode(favoriteLines, forKey: "favoriteLines")
        aCoder.encode(routeType, forKey: "routeType")
        aCoder.encode(bus, forKey: "bus")
        aCoder.encode(train, forKey: "train")
        aCoder.encode(subway, forKey: "subway")
        aCoder.encode(electric, forKey: "electric")
        aCoder.encode(sharedCar, forKey: "sharedCar")
        aCoder.encode(bike, forKey: "bike")
        aCoder.encode(electricScooter, forKey: "electricScooter")
        aCoder.encode((try? NSKeyedArchiver.archivedData(withRootObject: providers, requiringSecureCoding: false)) ?? Data(), forKey: "providersData")
    }
    
    public required init(coder decoder: NSCoder) {
        favoriteLines = decoder.decodeBool(forKey: "favoriteLines")
        routeType = decoder.decodeInteger(forKey: "routeType")
        bus = decoder.decodeBool(forKey: "bus")
        train = decoder.decodeBool(forKey: "train")
        subway = decoder.decodeBool(forKey: "subway")
        electric = decoder.decodeBool(forKey: "electric")
        sharedCar = decoder.decodeBool(forKey: "sharedCar")
        bike = decoder.decodeBool(forKey: "bike")
        electricScooter = decoder.decodeBool(forKey: "electricScooter")
        providers = (try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decoder.decodeData(forKey: "providersData"))) as? [Provider] ?? []
    }
}

let planRouteFilters: PlanRouteFilters = .init(favLines: true, rType: 1, bus: false, train: true, subway: false, electric: true, sCar: true, bike: true, eScooter: false, providers: [.init(name: "test"), .init(name: "drive")])
do {
    let data = try NSKeyedArchiver.archivedData(withRootObject: planRouteFilters, requiringSecureCoding: false)
    if let loadedFilters = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as? PlanRouteFilters {
        print("success", loadedFilters)
        print("favoriteLines", loadedFilters.favoriteLines)
        print("routeType", loadedFilters.routeType ?? "nil")
        print("bus", loadedFilters.bus)
        print("train", loadedFilters.train)
        print("subway", loadedFilters.subway)
        print("electric", loadedFilters.electric)
        print("sharedCar", loadedFilters.sharedCar)
        print("bike", loadedFilters.bike)
        print("electricScooter", loadedFilters.electricScooter)
        print("providers first", loadedFilters.providers.first?.name ?? "nil")
        print("providers last", loadedFilters.providers.last?.name ?? "nil")
    }
} catch {
    print(error)
}

这将打印出来

成功 <__lldb_expr_54.planroutefilters:>
最喜欢的线是真的
路线类型 1
巴士假
训练真
地铁假
电动真
共享汽车真
真正的自行车
电动滑板车假
供应商首次测试
供应商最后一次开车




更好/更简单的方法是坚持 Codable 协议并扩展 UserDefaults 以能够编码/解码 Codable 类型,如 this answer 所示:

extension UserDefaults {
    func decode<T: Decodable>(_ type: T.Type, forKey defaultName: String) throws -> T {
        try JSONDecoder().decode(T.self, from: data(forKey: defaultName) ?? .init())
    }
    func encode<T: Encodable>(_ value: T, forKey defaultName: String) throws {
        try set(JSONEncoder().encode(value), forKey: defaultName)
    }
}

【讨论】:

  • 我使用了您的第一个解决方案,但错误仍然发生。
  • 你遇到了什么错误?您可以发布您的 Provider 声明吗?
  • 同样的错误。我现在将使用提供程序代码更新问题
  • 您是否从代码中删除了aCoder.encode(self.providers, forKey: "providers")self.providers = decoder.decodeObject(forKey: "providers") as? [Provider]?顺便说一句,检查我的最后一次编辑,我的代码中有一些小错误。抱歉,我在浏览器中编写代码并没有对其进行测试。
  • 您实际上可以使用 object(forKey) == nil 检查 a 键是否有值,如果为真,则手动设置默认值。如果你不知道注册是如何工作的,你可以在这里查看stackoverflow.com/a/53684212/2303865
猜你喜欢
  • 1970-01-01
  • 2022-01-09
  • 2021-11-08
  • 1970-01-01
  • 2017-08-04
  • 2014-06-04
  • 1970-01-01
相关资源
最近更新 更多