在 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。
【问题讨论】:
您需要在Car
的init(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】:
我设法让这个工作。以下是更新后的GameScene
和Car
代码文件,其中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)
,您可能会注意到某些类型属性(如color
、position
或size
)在您归档/取消归档它们后会重置为其初始值.这是因为您现在提供了您自己的这两种方法的实现,因此您不能再依赖超类来为您处理类型属性的编码/解码。
您现在必须为您关心的所有属性实现编码/解码,包括您自己的自定义属性和类型的属性。例如:
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 设置未保存在框架内
检索 UserDefaults 后在 viewdidload 上刷新 collectionView