本文介绍了是否有更好的方法将自定义类保存到NSUserDefaults而不是使用NSCoder编码和解码所有内容?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我当前的类有大约50行只是编码和解码变量,以便我的类与NSUserDefaults兼容。有没有更好的方法来处理这个?

My current class has around 50 lines just encoding and decoding variables in order for my class to be NSUserDefaults compatible. Is there a better way to handle this?

示例:

 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")
}


推荐答案

您应该创建一个结构或枚举来组织您的密钥,因为您的方式很容易出现拼写错误。只需把它放在你的班级上方

You should create a struct or enum to organise your keys, because your way is just prone to typos. Just put it right above your class

enum Key: String {
  case allSettings

  case lightEnabled
  case soundEnabled
}

而不是像这样调用密钥

...forKey: Key.lightEnabled.rawValue)

现在关于你的问题,我的游戏尝试保存40个级别的属性(bestTimes,Level unlock status等)时遇到了同样的问题。我最初做了你尝试过的事情,这是纯粹的疯狂。

Now in regards to your question, I was facing the same issue with my game trying to save properties for 40 levels (bestTimes, Level unlock status etc). I initially did what you tried and it was pure madness.

我最终使用数组/字典甚至字典数组来处理我的数据,这样就减少了我的代码80百分。

I ended up using arrays/dictionaries or even arrays of dictionaries for my data which cut down my code by like 80 percent.

这也很好,就是说你需要保存像LevelUnlock bools这样的东西,它会让你以后的生活变得更轻松。在我的情况下,我有一个UnlockAllLevels按钮,现在我可以通过我的字典/数组循环,并在几行代码中更新/检查levelUnlock bools。比使用大量if-else或switch语句更好地分别检查每个属性。

Whats also nice about this is that say you need to save something like LevelUnlock bools, it will make your life so much easier later on. In my case I have a UnlockAllLevels button, and now I can just loop trough my dictionary/array and update/check the levelUnlock bools in a few lines of code. So much better than having massive if-else or switch statements to check each property individually.

例如

 var settingsDict = [
      Key.lightEnabled.rawValue: false,
      Key.soundEnabled.rawValue: false,
      ...
 ]

在解码器方法中你说这个

Than in the decoder method you say this

注意:这种方式会考虑到您可能会向SettingsDict添加新值,而不是在下一个应用程序启动时,这些值将不会被删除,因为您没有用保存的字典替换整个字典,您只更新值已经存在。

Note: This way will take into account that you might add new values to the SettingsDict and than on the next app launch those values will not be removed because you are not replacing the whole dictionary with the saved one, you only update the values that already exist.

 // 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($0) else {
           savedSettingsDict.removeValue(forKey: $0)
           return
       }
       settingsDict[$0] = $1
    }
}

如果使用多个字典而不是解码器方法将再次变得混乱你也会重复很多代码。为避免这种情况,您可以使用泛型创建NSCoder的扩展名。

If you use multiple dictionaries than your decoder method will become a messy again and you will also repeat alot of code. To avoid this you can create an extension of NSCoder using generics.

 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($0) else {
              savedData[$0] = nil
              return
          }

           newData[$0] = $1
       }

        return newData
     }
 }

并且你可以在每个字典的解码器方法中写这个。

and than you can write this in the decoder method for each dictionary.

settingsDict = aDecoder.decodeObject(settingsDict, forKey: Key.allSettings.rawValue)

您的编码器方法如下所示。

Your encoder method would look like this.

 encoder.encodeObject(settingsDict, forKey: Key.allSettings.rawValue)

在您的游戏/应用程序中,您可以像这样使用它们

In your game/app you you can use them like so

settingsDict[Key.lightEnabled.rawValue] = true

if settingsDict[Key.lightEnabled.rawValue] == true {
  /// light is turned on, do something
}

这种方式可以很容易地集成iCloud KeyValue存储(只需创建一个iCloud字典),再次主要是因为它很容易保存并用很少的代码比较很多值。

This way makes it also very easy to integrate iCloud KeyValue storage (just create an iCloud dictionary), again mainly because its so easy to save and compare a lot of values with very little code.

更新:

为了让调用这些更容易,我想在GameData类中创建一些方便的getter / setter。这样做的好处是,您可以更轻松地在项目中调用这些属性(就像您的旧方式),但您的编码/解码方法仍将保持紧凑。您还可以执行诸如循环以比较值之类的事情。

To make calling these a bit easier I like to create some convenience getters/setters in the GameData class. This has the benefit that you can more easily call these properties in your project (like your old way) but your encode/decode method will still stay compact. You can also still do things such as looping to compare values.

 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 }
}

并且您可以像普通属性一样调用它们。

and than you can call them like normal properties.

isLightEnabled = true

if isLightEnabled {
  /// light is turned on, do something
}

这篇关于是否有更好的方法将自定义类保存到NSUserDefaults而不是使用NSCoder编码和解码所有内容?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 14:17