如何将Swift枚举值存储在NSUserDefaults中?

59

我有一个枚举类型,类似这样:

enum Environment {
    case Production
    case Staging
    case Dev
}

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

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

我知道Swift枚举不是NSObject,这使得保存它变得困难,但我不确定将其转换为可存储对象的最佳方法是什么。

7个回答

89

使用rawValue来定义枚举是一种在NSUserDefaults中存储类型的方式,定义你的枚举来使用一个rawValue。Raw values可以是字符串、字符或任何整数或浮点数类型:

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

14
对于那些寻找阅读材料的人... myEnv = Environment(rawValue: defaults.stringForKey(kSavedEnvironmentDefaultsKey))! (翻译:如果有人在寻找阅读材料,下面是代码内容:myEnv = Environment(rawValue: defaults.stringForKey(kSavedEnvironmentDefaultsKey))!) - Maury Markowitz
2
对于任何在enum中使用rawValue初始化器或属性遇到问题的人,请确保您的enum扩展了您的rawValue的类型。例如,在OP的示例中,Environment扩展了String的类型。第一次我错过了这个细节。 - Craig Otis
1
由于您无法控制首选项中存储的内容,因此应检查defaults.stringForKey是否为nil,在这种情况下或者如果Environment最终为nil,则将其替换为默认值。 - gnasher729
2
如果您扩展的类型可以从StringInt进行文字转换,那么您甚至不需要为每个情况定义显式原始值,因为它们可以自动分配并与您扩展的类型一致:对于String枚举,它是包含情况名称的字符串;对于IntFloatDouble枚举,它是一个递增的数字。 - Antonio Favata
1
@AlexSmith,当您想要访问底层整数值时,仍应使用.rawValue(而不是Int(...)as Int)。我只是说当您定义ConnectionType枚举时,不需要为每个情况显式分配整数值。 - Antonio Favata
显示剩余3条评论

25

如果您想从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")
}

8

使用 Codable 协议

扩展 Environment 枚举,使其符合 Codable 协议,以将值编码和解码为 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")


当您有普通条目,如字符串,整数等时,它是否也适用? - Kai Zheng
2
当然可以。它们是原始类型并且已经符合 Codable。试一下就知道了。 - Omer Faruk Ozturk

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

}


2

这里有另一种替代方案,可以轻松地与基于类型的枚举(如String、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

0

我正在使用像这样的类型进行分段。能否请您尝试一下,它会对您有所帮助。

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

0

Swift 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接