背景
タイトルにもある通り、UserDefaultsにenumを保存したいシーンに遭遇しました。
enumを保存する際によくやる方法は次のようなコードではないでしょうか?
enum State: String {
case hoge, fuga, piyo
}
extension UserDefaults {
func setState(_ value: State?, forKey key: String) {
if let value = value {
set(value.rawValue, forKey: key)
} else {
removeSuite(named: key)
}
}
func getState(forKey key: String) -> State? {
if let string = string(forKey: key) {
return State(rawValue: string)
}
return nil
}
}
呼び出しは次のような感じです。
let key = "key"
var state: State? = UserDefaults.standard.getState(forKey: key) // nil
UserDefaults.standard.setState(State.hoge, forKey: key)
state = UserDefaults.standard.getState(forKey: key) // hoge
保存したいenumを値型enumにして、そのrawValueをUserDefaultsで読み書きする方法です。
これで正しくenumをUserDefaultsに保存できました。動かすだけならこれで十分です。
ですが、別の型のenumを保存する場合を考えましょう。そうなると、また上のコードのようにUserDefaultsにメソッドを追加する必要があります。保存したいenumの種類が増えるたびメソッドを追加するのは面倒です。
解決方法
複数種類のenumを愚直に保存する場合、基本的には上のコードの型が違うだけのコードが増えます。
ということでジェネリクスを利用していきます。
値型enumの正体はRawRepresentableというプロトコルです。
public protocol RawRepresentable {
associatedtype RawValue
public init?(rawValue: Self.RawValue)
public var rawValue: Self.RawValue { get }
}
このプロトコルを元にUserDefaultsにメソッドを追加します。
extension UserDefaults {
func setEnum<T: RawRepresentable>(_ value: T?, forKey key: String) where T.RawValue == String {
if let value = value {
set(value.rawValue, forKey: key)
} else {
removeObject(forKey: key)
}
}
func getEnum<T: RawRepresentable>(forKey key: String) -> T? where T.RawValue == String {
if let string = string(forKey: key) {
return T(rawValue: string)
}
return nil
}
}
今回はRawValueがStringの場合のメソッドのみを追加していますが、基本的にはこれで十分だと思います。
呼び出し側のコードは次の通りです。
let key = "key"
var state: State? = UserDefaults.standard.getEnum(forKey: key) // nil
UserDefaults.standard.setEnum(State.hoge, forKey: key)
state = UserDefaults.standard.getEnum(forKey: key) // hoge
もちろんState以外のenumでもRawValueがStringであれば、UserDefaultsにメソッドを追加することなく読み書きすることができます。
以上、ちょっとした小ネタでした。
「もっとこうした方がいい」などありましたらコメントお願いします。