在 UserDefaults 上使用 NSKeyedArchiver 时属性重置

Posted

技术标签:

【中文标题】在 UserDefaults 上使用 NSKeyedArchiver 时属性重置【英文标题】:Properties reset when using NSKeyedArchiver on UserDefaults 【发布时间】:2018-01-11 02:16:56 【问题描述】:

我有一个 SKSpriteNode 对象数组,我想将它们持久化到 UserDefaults。在下面的代码 (or see demo project on GitHub) 中,我使用 NSKeyedUnarchiver 将数组编码为数据,然后再将其设置为默认值。但是当我取消归档数据时,对象的engineSize 属性被重置为默认值 0。

Car.swift

import SpriteKit

class Car: SKSpriteNode 
    var engineSize: Int = 0

    init() 
        super.init(texture: nil, color: .blue, size: CGSize(width: 100, height: 100))
    

    required init?(coder aDecoder: NSCoder) 
        super.init(coder: aDecoder)
    

GameScene.swift

import SpriteKit

class GameScene: SKScene 
    let defaults = UserDefaults.standard
    var carArray = [Car]()

    override func didMove(to view: SKView) 
        for _ in 1...3 
            let car = Car()
            car.engineSize = 2000
            carArray.append(car)
        

        // Save to defaults
        defaults.set(NSKeyedArchiver.archivedData(withRootObject: carArray), forKey: "carArrayKey")

        // Restore from defaults
        let arrayData = defaults.data(forKey: "carArrayKey")
        carArray = NSKeyedUnarchiver.unarchiveObject(with: arrayData!) as! [Car]
    

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
        for c in carArray 
            print("Car's engine size is \(c.engineSize)")
        
    

This answer 引导我尝试在 Car 类上实现编码器/解码器方法:

Car.swift(更新)

import SpriteKit

class Car: SKSpriteNode 
    var engineSize: Int = 0

    init() 
        super.init(texture: nil, color: .blue, size: CGSize(width: 100, height: 100))
    

    required convenience public init(coder decoder: NSCoder) 
        self.init()

        if let engineSize = decoder.decodeObject(forKey: "engineSize") as? Int 
            self.engineSize = engineSize
        
    

    func encodeWithCoder(coder : NSCoder) 
        coder.encode(self.engineSize, forKey: "engineSize")
    

但是,这似乎不起作用。 GameScene 仍在将 engineSize 打印为 0。我执行编码器/解码器是否错误?当engineSize 属性从默认值恢复时,我可以做些什么来防止它们重置为 0?

更新

这是我更新后的 Car 课程,因为我正试图让它与 rmaddy 的建议一起使用:

import SpriteKit

class Car: SKSpriteNode 
    var engineSize: Int = 0

    init() 
        super.init(texture: nil, color: .blue, size: CGSize(width: 100, height: 100))
    

    required init?(coder decoder: NSCoder) 
        super.init(coder: decoder)
        if let engineSize = decoder.decodeObject(forKey: "engineSize") as? Int 
            self.engineSize = engineSize
        
    

    func encodeWithCoder(coder : NSCoder) 
        super.encode(with: coder)
        coder.encode(self.engineSize, forKey: "engineSize")
    

代码编译并运行,但engineSize 属性在保存为默认值时仍被重置为 0。

【问题讨论】:

您需要在Carinit(coder:)encodeWithCoder(coder:) 方法中调用super 您还应该删除并重新安装您的应用程序,以确保之前尝试的 UserDefaults 中没有错误数据。 我添加了super.encode(with: coder) 好的,但是在init(coder:) 中,编译器不喜欢调用super.init(decoder)。它抱怨:Convenience initializer for 'Car' must delegate (with 'self.init') rather than chaining to a superclass initializer (with 'super.init') init(coder:) 不应标记为便利初始化程序。 @rmaddy 删除 convenience 会产生错误。我更新了帖子以显示我正在尝试的代码。 【参考方案1】:

我设法让这个工作。以下是更新后的GameSceneCar 代码文件,其中NSCoding 已正确实现。

Car.swift

import SpriteKit

class Car: SKSpriteNode 
    var engineSize: Int = 0
    
    init() 
        super.init(texture: nil, color: .blue, size: CGSize(width: 100, height: 100))
    
    
    required init(coder aDecoder: NSCoder) 
        engineSize = aDecoder.decodeInteger(forKey: "engineSize")
        super.init(texture: nil, color: .blue, size: CGSize(width: 100, height: 100))
    
    
    override func encode(with aCoder: NSCoder) 
        aCoder.encode(engineSize, forKey: "engineSize")
    

GameScene.swift

import SpriteKit

class GameScene: SKScene 
    let defaults = UserDefaults.standard
    var carArray = [Car]()
    
    override func didMove(to view: SKView) 
        for _ in 1...3 
            let car = Car()
            car.engineSize = 2000
            carArray.append(car)
        
        
        defaults.set(NSKeyedArchiver.archivedData(withRootObject: carArray), forKey: "carArrayKey")
        
        let arrayData = defaults.data(forKey: "carArrayKey")
        carArray = NSKeyedUnarchiver.unarchiveObject(with: arrayData!) as! [Car]
    
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) 
        for c in carArray 
            print("Car's engine size is \(c.engineSize)")
        
    

更新

如果您如上所示实现init(coder aDecoder: NSCoder)encode(with aCoder: NSCoder),您可能会注意到某些类型属性(如colorpositionsize)在您归档/取消归档它们后会重置为其初始值.这是因为您现在提供了您自己的这两种方法的实现,因此您不能再依赖超类来为您处理类型属性的编码/解码。

您现在必须为您关心的所有属性实现编码/解码,包括您自己的自定义属性和类型的属性。例如:

required init(coder aDecoder: NSCoder) 
    super.init(texture: nil, color: .blue, size: CGSize(width: 100, height: 100))
    engineSize = aDecoder.decodeInteger(forKey: "engineSize")
    self.position = aDecoder.decodeCGPoint(forKey: "position")
    self.alpha = aDecoder.decodeObject(forKey: "alpha") as! CGFloat
    self.zPosition = aDecoder.decodeObject(forKey: "zPosition") as! CGFloat
    self.name = aDecoder.decodeObject(forKey: "name") as! String?
    self.colorBlendFactor = aDecoder.decodeObject(forKey: "colorBlendFactor") as! CGFloat
    self.color = aDecoder.decodeObject(forKey: "color") as! UIColor
    self.size = aDecoder.decodeCGSize(forKey: "size")
    self.texture = aDecoder.decodeObject(forKey: "texture") as! SKTexture?


override func encode(with aCoder: NSCoder) 
    aCoder.encode(engineSize, forKey: "engineSize")
    aCoder.encode(self.position, forKey: "position")
    aCoder.encode(self.alpha, forKey: "alpha")
    aCoder.encode(self.zPosition, forKey: "zPosition")
    aCoder.encode(self.name, forKey: "name")
    aCoder.encode(self.colorBlendFactor, forKey: "colorBlendFactor")
    aCoder.encode(self.color, forKey: "color")
    aCoder.encode(self.size, forKey: "size")
    aCoder.encode(self.texture, forKey: "texture")

【讨论】:

以上是关于在 UserDefaults 上使用 NSKeyedArchiver 时属性重置的主要内容,如果未能解决你的问题,请参考以下文章

iOS 13 UserDefaults:在某些设备上启动时应用程序崩溃

Swift - UserDefaults 设置未保存在框架内

text 在操场上嘲笑UserDefaults

检索 UserDefaults 后在 viewdidload 上刷新 collectionView

SwiftUI:如何使用 UserDefaults 持久化 @Published 变量?

UserDefaults 没有第一次加载