有没有比使用 NSCoder 编码和解码所有内容更好的方法将自定义 class 保存到 NSUserDefaults?
Is there a better way to save a custom class to NSUserDefaults than encoding and decoding everything with NSCoder?
我当前的 class 有大约 50 行编码和解码变量,以便我的 class 与 NSUserDefaults 兼容。有没有更好的方法来处理这个问题?
示例:
init(coder aDecoder: NSCoder!) {
lightEnabled = aDecoder.decodeBoolForKey("lightEnabled")
soundEnabled = aDecoder.decodeBoolForKey("soundEnabled")
vibrateEnabled = aDecoder.decodeBoolForKey("vibrateEnabled")
pulseEnabled = aDecoder.decodeBoolForKey("pulseEnabled")
songs = aDecoder.decodeObjectForKey("songs") as! [Song]
currentSong = aDecoder.decodeIntegerForKey("currentSong")
enableBackgroundSound = aDecoder.decodeBoolForKey("enableBackgroundSound")
mixSound = aDecoder.decodeBoolForKey("mixSound")
playSoundInBackground = aDecoder.decodeBoolForKey("playSoundInBackground")
duckSounds = aDecoder.decodeBoolForKey("duckSounds")
BPMBackground = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("BPMBackgorund") as! NSData) as! UIColor!
BPMPulseColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("BPMPulseColor") as! NSData) as! UIColor!
TempoBackGround = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TempoBackGround") as! NSData) as! UIColor!
TempoPulseColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TempoPulseColor") as! NSData) as! UIColor!
TimeBackGround = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TimeBackGround") as! NSData) as! UIColor!
TimeStrokeColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TimeStrokeColor") as! NSData) as! UIColor!
TextColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TextColor") as! NSData) as! UIColor!
}
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeBool(lightEnabled, forKey: "lightEnabled")
aCoder.encodeBool(soundEnabled, forKey: "soundEnabled")
aCoder.encodeBool(vibrateEnabled, forKey: "vibrateEnabled")
aCoder.encodeBool(pulseEnabled, forKey: "pulseEnabled")
aCoder.encodeObject(songs, forKey: "songs")
aCoder.encodeInteger(currentSong, forKey: "currentSong")
aCoder.encodeBool(enableBackgroundSound, forKey: "enableBackgroundSound")
aCoder.encodeBool(mixSound, forKey: "mixSound")
aCoder.encodeBool(playSoundInBackground, forKey: "playSoundInBackground")
aCoder.encodeBool(duckSounds, forKey: "duckSounds")
aCoder.encodeObject(BPMBackground.archivedData(), forKey: "BPMBackground")
aCoder.encodeObject(BPMPulseColor.archivedData(), forKey: "BPMPulseColor")
aCoder.encodeObject(TempoBackGround.archivedData(), forKey: "TempoBackGround")
aCoder.encodeObject(TempoPulseColor.archivedData(), forKey: "TempoPulseColor")
aCoder.encodeObject(TimeBackGround.archivedData(), forKey: "TimeBackGround")
aCoder.encodeObject(TimeStrokeColor.archivedData(), forKey: "TimeStrokeColor")
aCoder.encodeObject(TextColor.archivedData(), forKey: "TextColor")
}
您应该创建一个结构或枚举来组织您的密钥,因为您的方式很容易出现拼写错误。把它放在 class
的正上方
enum Key: String {
case allSettings
case lightEnabled
case soundEnabled
}
而不只是像这样调用键
...forKey: Key.lightEnabled.rawValue)
关于您的问题,我在尝试保存 40 个关卡的属性(最佳时间、关卡解锁状态等)时遇到了同样的问题。我最初做了你尝试过的事情,这简直是疯狂。
我最终使用 arrays/dictionaries 甚至字典数组来存储我的数据,这将我的代码减少了大约 80%。
还有一个好处就是说你需要保存像 LevelUnlock bools 这样的东西,它会让你以后的生活变得更加轻松。在我的例子中,我有一个 UnlockAllLevels 按钮,现在我可以在几行代码中遍历我的 dictionary/array 和 update/check levelUnlock 布尔值。比使用大量 if-else 或 switch 语句来单独检查每个 属性 要好得多。
例如
var settingsDict = [
Key.lightEnabled.rawValue: false,
Key.soundEnabled.rawValue: false,
...
]
比你在decoder方法中说的这个
注意:这种方式会考虑到您可能会向 SettingsDict 添加新值,并且在下一次应用程序启动时不会删除这些值,因为您不会用保存的字典替换整个字典,您只是更新已经存在的值。
// If no saved data found do nothing
if var savedSettingsDict = decoder.decodeObjectForKey(Key.allSettings.rawValue) as? [String: Bool] {
// Update the dictionary values with the previously saved values
savedSettingsDict.forEach {
// If the key does not exist anymore remove it from saved data.
guard settingsDict.keys.contains([=13=]) else {
savedSettingsDict.removeValue(forKey: [=13=])
return
}
settingsDict[[=13=]] =
}
}
如果你使用多个词典,那么你的解码器方法会再次变得混乱,你也会重复很多代码。为避免这种情况,您可以使用泛型创建 NSCoder 的扩展。
extension NSCoder {
func decodeObject<T>(_ object: [String: T], forKey key: String) -> [String: T] {
guard var savedData = decodeObject(forKey: key) as? [String: T] else { return object }
var newData = object
savedData.forEach {
guard object.keys.contains([=14=]) else {
savedData[[=14=]] = nil
return
}
newData[[=14=]] =
}
return newData
}
}
然后你可以在每个字典的解码器方法中写这个。
settingsDict = aDecoder.decodeObject(settingsDict, forKey: Key.allSettings.rawValue)
您的编码器方法如下所示。
encoder.encodeObject(settingsDict, forKey: Key.allSettings.rawValue)
在你的 game/app 你可以像这样使用它们
settingsDict[Key.lightEnabled.rawValue] = true
if settingsDict[Key.lightEnabled.rawValue] == true {
/// light is turned on, do something
}
这种方式也使得集成 iCloud KeyValue 存储变得非常容易(只需创建一个 iCloud 字典),同样主要是因为它很容易用很少的代码保存和比较大量的值。
更新:
为了更容易调用这些,我想在 GameData class 中创建一些便利 getters/setters。这样做的好处是您可以更轻松地在项目中调用这些属性(就像您的旧方法一样),但您的 encode/decode 方法仍将保持紧凑。您还可以执行诸如循环比较值之类的操作。
var isLightEnabled: Bool {
get { return settingsDict[Key.lightEnabled.rawValue] ?? false }
set { settingsDict[Key.lightEnabled.rawValue] = newValue }
}
var isSoundEnabled: Bool {
get { return settingsDict[Key.soundEnabled.rawValue] ?? false }
set { settingsDict[Key.soundEnabled.rawValue] = newValue }
}
而且您可以像普通属性一样调用它们。
isLightEnabled = true
if isLightEnabled {
/// light is turned on, do something
}
查看protocol codeable
Swift 4.
将为您自动生成解码器和编码器。
签出:(大约从一半开始)
https://developer.apple.com/videos/play/wwdc2017/212/
我当前的 class 有大约 50 行编码和解码变量,以便我的 class 与 NSUserDefaults 兼容。有没有更好的方法来处理这个问题?
示例:
init(coder aDecoder: NSCoder!) {
lightEnabled = aDecoder.decodeBoolForKey("lightEnabled")
soundEnabled = aDecoder.decodeBoolForKey("soundEnabled")
vibrateEnabled = aDecoder.decodeBoolForKey("vibrateEnabled")
pulseEnabled = aDecoder.decodeBoolForKey("pulseEnabled")
songs = aDecoder.decodeObjectForKey("songs") as! [Song]
currentSong = aDecoder.decodeIntegerForKey("currentSong")
enableBackgroundSound = aDecoder.decodeBoolForKey("enableBackgroundSound")
mixSound = aDecoder.decodeBoolForKey("mixSound")
playSoundInBackground = aDecoder.decodeBoolForKey("playSoundInBackground")
duckSounds = aDecoder.decodeBoolForKey("duckSounds")
BPMBackground = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("BPMBackgorund") as! NSData) as! UIColor!
BPMPulseColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("BPMPulseColor") as! NSData) as! UIColor!
TempoBackGround = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TempoBackGround") as! NSData) as! UIColor!
TempoPulseColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TempoPulseColor") as! NSData) as! UIColor!
TimeBackGround = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TimeBackGround") as! NSData) as! UIColor!
TimeStrokeColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TimeStrokeColor") as! NSData) as! UIColor!
TextColor = NSKeyedUnarchiver.unarchiveObjectWithData(aDecoder.decodeObjectForKey("TextColor") as! NSData) as! UIColor!
}
func encodeWithCoder(aCoder: NSCoder!) {
aCoder.encodeBool(lightEnabled, forKey: "lightEnabled")
aCoder.encodeBool(soundEnabled, forKey: "soundEnabled")
aCoder.encodeBool(vibrateEnabled, forKey: "vibrateEnabled")
aCoder.encodeBool(pulseEnabled, forKey: "pulseEnabled")
aCoder.encodeObject(songs, forKey: "songs")
aCoder.encodeInteger(currentSong, forKey: "currentSong")
aCoder.encodeBool(enableBackgroundSound, forKey: "enableBackgroundSound")
aCoder.encodeBool(mixSound, forKey: "mixSound")
aCoder.encodeBool(playSoundInBackground, forKey: "playSoundInBackground")
aCoder.encodeBool(duckSounds, forKey: "duckSounds")
aCoder.encodeObject(BPMBackground.archivedData(), forKey: "BPMBackground")
aCoder.encodeObject(BPMPulseColor.archivedData(), forKey: "BPMPulseColor")
aCoder.encodeObject(TempoBackGround.archivedData(), forKey: "TempoBackGround")
aCoder.encodeObject(TempoPulseColor.archivedData(), forKey: "TempoPulseColor")
aCoder.encodeObject(TimeBackGround.archivedData(), forKey: "TimeBackGround")
aCoder.encodeObject(TimeStrokeColor.archivedData(), forKey: "TimeStrokeColor")
aCoder.encodeObject(TextColor.archivedData(), forKey: "TextColor")
}
您应该创建一个结构或枚举来组织您的密钥,因为您的方式很容易出现拼写错误。把它放在 class
的正上方enum Key: String {
case allSettings
case lightEnabled
case soundEnabled
}
而不只是像这样调用键
...forKey: Key.lightEnabled.rawValue)
关于您的问题,我在尝试保存 40 个关卡的属性(最佳时间、关卡解锁状态等)时遇到了同样的问题。我最初做了你尝试过的事情,这简直是疯狂。
我最终使用 arrays/dictionaries 甚至字典数组来存储我的数据,这将我的代码减少了大约 80%。
还有一个好处就是说你需要保存像 LevelUnlock bools 这样的东西,它会让你以后的生活变得更加轻松。在我的例子中,我有一个 UnlockAllLevels 按钮,现在我可以在几行代码中遍历我的 dictionary/array 和 update/check levelUnlock 布尔值。比使用大量 if-else 或 switch 语句来单独检查每个 属性 要好得多。
例如
var settingsDict = [
Key.lightEnabled.rawValue: false,
Key.soundEnabled.rawValue: false,
...
]
比你在decoder方法中说的这个
注意:这种方式会考虑到您可能会向 SettingsDict 添加新值,并且在下一次应用程序启动时不会删除这些值,因为您不会用保存的字典替换整个字典,您只是更新已经存在的值。
// If no saved data found do nothing
if var savedSettingsDict = decoder.decodeObjectForKey(Key.allSettings.rawValue) as? [String: Bool] {
// Update the dictionary values with the previously saved values
savedSettingsDict.forEach {
// If the key does not exist anymore remove it from saved data.
guard settingsDict.keys.contains([=13=]) else {
savedSettingsDict.removeValue(forKey: [=13=])
return
}
settingsDict[[=13=]] =
}
}
如果你使用多个词典,那么你的解码器方法会再次变得混乱,你也会重复很多代码。为避免这种情况,您可以使用泛型创建 NSCoder 的扩展。
extension NSCoder {
func decodeObject<T>(_ object: [String: T], forKey key: String) -> [String: T] {
guard var savedData = decodeObject(forKey: key) as? [String: T] else { return object }
var newData = object
savedData.forEach {
guard object.keys.contains([=14=]) else {
savedData[[=14=]] = nil
return
}
newData[[=14=]] =
}
return newData
}
}
然后你可以在每个字典的解码器方法中写这个。
settingsDict = aDecoder.decodeObject(settingsDict, forKey: Key.allSettings.rawValue)
您的编码器方法如下所示。
encoder.encodeObject(settingsDict, forKey: Key.allSettings.rawValue)
在你的 game/app 你可以像这样使用它们
settingsDict[Key.lightEnabled.rawValue] = true
if settingsDict[Key.lightEnabled.rawValue] == true {
/// light is turned on, do something
}
这种方式也使得集成 iCloud KeyValue 存储变得非常容易(只需创建一个 iCloud 字典),同样主要是因为它很容易用很少的代码保存和比较大量的值。
更新:
为了更容易调用这些,我想在 GameData class 中创建一些便利 getters/setters。这样做的好处是您可以更轻松地在项目中调用这些属性(就像您的旧方法一样),但您的 encode/decode 方法仍将保持紧凑。您还可以执行诸如循环比较值之类的操作。
var isLightEnabled: Bool {
get { return settingsDict[Key.lightEnabled.rawValue] ?? false }
set { settingsDict[Key.lightEnabled.rawValue] = newValue }
}
var isSoundEnabled: Bool {
get { return settingsDict[Key.soundEnabled.rawValue] ?? false }
set { settingsDict[Key.soundEnabled.rawValue] = newValue }
}
而且您可以像普通属性一样调用它们。
isLightEnabled = true
if isLightEnabled {
/// light is turned on, do something
}
查看protocol codeable
Swift 4.
将为您自动生成解码器和编码器。
签出:(大约从一半开始) https://developer.apple.com/videos/play/wwdc2017/212/