【问题标题】:How can I store a Swift enum value in NSUserDefaults如何在 NSUserDefaults 中存储 Swift 枚举值
【发布时间】:2015-01-30 23:13:30
【问题描述】:

我有一个这样的枚举:

enum Environment {
    case Production
    case Staging
    case Dev
}

我想像这样在 NSUserDefaults 中保存一个实例:

func saveEnvironment(environment : Environment){
    NSUserDefaults.standardUserDefaults().setObject(environment, forKey: kSavedEnvironmentDefaultsKey)
}

我知道 Swift 枚举不是 NSObject,因此很难保存,但我不确定将其转换为可存储的最佳方法是什么。

【问题讨论】:

    标签: swift


    【解决方案1】:

    使用Codable 协议

    符合Codable 协议的范围Environment 枚举以将值编码和解码为Data

    enum Environment: String, Codable {
        case Production
        case Staging
        case Dev
    }
    
    

    UserDefaults 的包装器:

    struct UserDefaultsManager {
        static var userDefaults: UserDefaults = .standard
        
        static func set<T>(_ value: T, forKey: String) where T: Encodable {
            if let encoded = try? JSONEncoder().encode(value) {
                userDefaults.set(encoded, forKey: forKey)
            }
        }
        
        static func get<T>(forKey: String) -> T? where T: Decodable {
            guard let data = userDefaults.value(forKey: forKey) as? Data,
                let decodedData = try? JSONDecoder().decode(T.self, from: data)
                else { return nil }
            return decodedData
        }
    }
    

    用法

    // Set
    let environment: Environment = .Production
    UserDefaultsManager.set(environment, forKey: "environment")
    
    // Get
    let environment: Environment? = UserDefaultsManager.get(forKey: "environment")
    
    

    【讨论】:

    • 当您有字符串、整数等普通条目时,它是否也有效?
    • 当然。它们是原始类型并且已经符合Codable。试试看吧。
    【解决方案2】:

    Swift 5.1 您可以创建一个通用属性包装器,使用 Codable 将值转换进出 UserDefaults

    extension UserDefaults {
        // let value: Value already set somewhere
        // UserDefaults.standard.set(newValue, forKey: "foo")
        //
        func set<T>(_ value: T, forKey: String) where T: Encodable {
            if let encoded = try? JSONEncoder().encode(value) {
                setValue(encoded, forKey: forKey)
            }
        }
    
        // let value: Value? = UserDefaults.standard.get(forKey: "foo")
        //
        func get<T>(forKey: String) -> T? where T: Decodable {
            guard let data = value(forKey: forKey) as? Data,
                let decodedData = try? JSONDecoder().decode(T.self, from: data)
                else { return nil }
            return decodedData
        }
    }
    
    @propertyWrapper
    public struct UserDefaultsBacked<Value>: Equatable where Value: Equatable, Value: Codable {
        let key: String
        let defaultValue: Value
        var storage: UserDefaults = .standard
    
        public init(key: String, defaultValue: Value) {
            self.key = key
            self.defaultValue = defaultValue
        }
    
        // if the value is nil return defaultValue
        // if the value empty return defaultValue
        // otherwise return the value
        //
        public var wrappedValue: Value {
            get {
                let value: Value? = storage.get(forKey: key)
                if let stringValue = value as? String, stringValue.isEmpty {
                    // for string values we want to equate nil with empty string as well
                    return defaultValue
                }
                return value ?? defaultValue
            }
            set {
                storage.set(newValue, forKey: key)
                storage.synchronize()
            }
        }
    }
    
    // use it
    struct AppState: Equatable {
        enum TabItem: String, Codable {
            case home
            case book
            case trips
            case account
        }
        var isAppReady = false
    
        @UserDefaultsBacked(key: "selectedTab", defaultValue: TabItem.home)
        var selectedTab
        // default value will be TabItem.home
    
        @UserDefaultsBacked(key: "selectedIndex", defaultValue: 33)
        var selectedIndex
        // default value will be 33
    
    }
    
    

    【讨论】:

      【解决方案3】:

      如果您想从 UserDefaults 保存/读取数据并分离一些逻辑,您可以通过以下方式(Swift 3)进行:

      enum Environment: String {
          case Production
          case Staging
          case Dev
      }
      
      class UserDefaultsManager {
      
          static let shared = UserDefaultsManager()
      
          var environment: Environment? {
             get {
                 guard let environment = UserDefaults.standard.value(forKey: kSavedEnvironmentDefaultsKey) as? String else {
                     return nil
                 }
                 return Environment(rawValue: environment)
             }
             set(environment) {
                 UserDefaults.standard.set(environment?.rawValue, forKey: kSavedEnvironmentDefaultsKey)
             }
          }
      }
      

      所以在 UserDefaults 中保存数据会是这样的:

      UserDefaultsManager.shared.environment = Environment.Production
      

      并读取数据,以这种方式保存在UserDefaults中:

      if let environment = UserDefaultsManager.shared.environment {
          //you can do some magic with this variable
      } else {
          print("environment data not saved in UserDefaults")
      }
      

      【讨论】:

        【解决方案4】:

        这是另一种替代方法,可以很容易地与基于类型(如字符串、Int 等)的枚举一起使用,这些类型可以由 NSUserDefaults 存储。

        @propertyWrapper
        struct StoredProperty<T: RawRepresentable> {
            let key: String
            let defaultValue: T
        
            init(_ key: String, defaultValue: T) {
                self.key = key
                self.defaultValue = defaultValue
            }
        
            var wrappedValue: T {
                get {
                    guard let rawValue = UserDefaults.standard.object(forKey: key) as? T.RawValue, let value = T(rawValue: rawValue) else {
                         return defaultValue
                    }
                    return value
                }
                set {
                    UserDefaults.standard.set(newValue.rawValue, forKey: key)
                }
            }
        }
        

        示例用法:

        enum Environment: String {
            case Production
            case Staging
            case Dev
        }
        
        @StoredProperty("Environment", defaultValue: .Dev)
        var storedProperty: Environment
        

        【讨论】:

          【解决方案5】:

          对枚举使用 rawValue 是使用可以存储在 NSUserDefaults 中的类型的一种方式,定义您的枚举以使用 rawValue。原始值可以是字符串、字符或任何整数或浮点数类型:

          enum Environment: String {
              case Production = "Prod"
              case Staging    = "Stg"
              case Dev        = "Dev"
          }
          

          您也可以直接使用 rawValue(可能来自 NSUserDefaults)创建一个枚举实例,例如:

          let env = Environment(rawValue: "Dev")
          

          您可以像这样从枚举对象中提取 rawValue(字符串),然后在需要时将其存储在 NSUserDefaults 中:

          if let myEnv = env {
              println(myEnv.rawValue)
          }
          
          
          func saveEnvironment(environment : Environment){
              NSUserDefaults.standardUserDefaults().setObject(environment.rawValue, forKey: kSavedEnvironmentDefaultsKey)
          }
          

          【讨论】:

          • 对于那些寻找阅读的人... myEnv = Environment(rawValue: defaults.stringForKey(kSavedEnvironmentDefaultsKey))!
          • 对于任何对枚举中的rawValue 初始化程序或属性有问题的人,请确保您的枚举扩展了rawValue 的类型。例如,在 OP 的示例中,Environment 扩展了 String。我第一次错过了这个。
          • 由于您无法控制首选项中存储的内容,您应该检查 defaults.stringForKey 是否为 nil,在这种情况下,或者如果 Environment 最终为 nil,请将其替换为默认值。
          • 如果您扩展的类型可以从 StringInt 进行字面转换,您甚至不需要为每种情况定义显式原始值,因为它们可以自动分配您扩展的类型:包含String 枚举的案例名称的字符串; IntFloatDouble 枚举的累进数字。
          • @AlexSmith,当您想要访问底层整数值时,您仍然应该使用.rawValue(而不是Int(...)as Int)。我只是说,当您定义 ConnectionType 枚举时,您不需要为每个案例显式分配一个整数值。
          【解决方案6】:

          我正在使用这种类型的分期。你可以试试这个,它会帮助你。

          enum Environment: String {
            case Production  = "Production URL"
            case Testing     = "Testing URl"
            case Development = "Development URL"
          }
          //your button actions
           // MARK: set Development api
            @IBAction func didTapDevelopmentAction(_ sender: Any) {
              let env = Environment.Development.rawValue
              print(env)
              UserDefaults.standard.set(env, forKey:Key.UserDefaults.stagingURL)
            }
          // MARK: set Production api
            @IBAction func didTapProductionAction(_ sender: Any) {
              let env = Environment.Production.rawValue
              print(env)
              UserDefaults.standard.set(env, forKey:Key.UserDefaults.stagingURL)
            }
          
            // MARK: set Testing api
            @IBAction func didTapTestingAction(_ sender: Any) {
              let env = Environment.Testing.rawValue
              print(env)
              UserDefaults.standard.set(env, forKey:Key.UserDefaults.stagingURL)
            }
          //Based on selection act
          print("\(UserDefaults.standard.object(forKey: "stagingURL") ?? "")")
          

          【讨论】:

            【解决方案7】:

            斯威夫特 5.1 您可以为此创建属性包装器

            @propertyWrapper final class UserDefaultsLanguageValue {
                var defaultValue: LanguageType
                var key: UserDefaultsKey
            
                init(key: UserDefaultsKey, defaultValue: LanguageType) {
                    self.key = key
                    self.defaultValue = defaultValue
                }
            
                var wrappedValue: LanguageType {
                    get { LanguageType(rawValue: UserDefaults.standard.object(forKey: key.rawValue) as? String ?? defaultValue.rawValue) ?? .en }
                    set { UserDefaults.standard.set(newValue.rawValue, forKey: key.rawValue) }
                }
            }
            
            enum UserDefaultsKey: String {
                case language
            }
            
            enum LanguageType: String {
                case en
                case ar
            }
            

            然后就这样使用它

            @UserDefaultsLanguageValue(key: .language, defaultValue: LanguageType.en) var language
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2018-08-18
              • 2019-10-02
              • 2012-11-17
              • 2015-11-12
              • 1970-01-01
              • 2020-08-31
              • 2017-03-14
              相关资源
              最近更新 更多