UserDefaultsをSwiftのEnumで扱えるように拡張する
UserDefaultsって便利ですよね。
基本的にはユーザーのアプリ内の設定値保存に使うことを主としていますが、
キルされても保持したいけど、アンインストール→インストールでは消されてもいい。DBを作るまでもないといった
値の軽いキャッシュとして利用したりもできます。
UserDefaultsのキーはString...
Stringって安易に使うとバギーですよね。
どこかでKeyを集約書けばよくね?って思いますがそれってつまり列挙なんです。 ということで、enumでラップして使ってみました。
アプローチ
- キーのprotocol
UserDefaultKey
を定義 UserDefaultKey
を引数にとって各々の型の値を返すextensionを定義UserDefaultKey
を使ったUserDefaultのデータストアの実装
1. キーのprotocol UserDefaultKey
を定義
public protocol UserDefaultKey: RawRepresentable, CustomStringConvertible { } public extension UserDefaultKey where RawValue == String { public var description: String { return self.rawValue } }
Enumって実はよしなにコンパイラが作ってくれるEquatable
とRawRepresentable
に準拠しているstructのstaticな変数の列挙のようなものです。
そこでEnumを包含する方の定義としてRawRepresentable
を定義しておきます。
今回はCustomStringConvertible.description
をキーとして使う戦略です。
なので、RawValueがStringのときはdescriptionの値としてself.rawValue
を返せばいいことになります
したがって下記のようにenumを作るだけで、キーとして扱えるということです
enum DefaultKeys: String, UserDefaultKey { case autoReloadEnabled }
2. UserDefaultKey
を引数にとって各々の型の値を返すextensionを定義
UserDefaultKeyを受け取れるインターフェースとして定義しています。
これで透過的にUserDefaultKey
をキーとして扱えるようになりました
// MARK: getter public extension UserDefaults { public func object<Key: UserDefaultKey>(forKey key: Key) -> Any? { return object(forKey: key.description) } public func url<Key: UserDefaultKey>(forKey key: Key) -> URL? { return url(forKey: key.description) } public func array<Key: UserDefaultKey>(forKey key: Key) -> [Any]? { return array(forKey: key.description) } public func dictionary<Key: UserDefaultKey>(forKey key: Key) -> [String: Any]? { return dictionary(forKey: key.description) } public func string<Key: UserDefaultKey>(forKey key: Key) -> String? { return string(forKey: key.description) } public func stringArray<Key: UserDefaultKey>(forKey key: Key) -> [String]? { return stringArray(forKey: key.description) } public func data<Key: UserDefaultKey>(forKey key: Key) -> Data? { return data(forKey: key.description) } public func bool<Key: UserDefaultKey>(forKey key: Key) -> Bool? { return bool(forKey: key.description) } public func integer<Key: UserDefaultKey>(forKey key: Key) -> Int? { return integer(forKey: key.description) } public func float<Key: UserDefaultKey>(forKey key: Key) -> Float? { return float(forKey: key.description) } public func double<Key: UserDefaultKey>(forKey key: Key) -> Double? { return double(forKey: key.description) } } // MARK: - setter public extension UserDefaults { public func set<Key: UserDefaultKey>(_ value: Any?, forKey key: Key) { set(value, forKey: key.description) } public func set<Key: UserDefaultKey>(_ value: URL?, forKey key: Key) { set(value, forKey: key.description) } public func set<Key: UserDefaultKey>(_ value: Bool, forKey key: Key) { set(value, forKey: key.description) } public func set<Key: UserDefaultKey>(_ value: Int, forKey key: Key) { set(value, forKey: key.description) } public func set<Key: UserDefaultKey>(_ value: Float, forKey key: Key) { set(value, forKey: key.description) } public func set<Key: UserDefaultKey>(_ value: Double, forKey key: Key) { set(value, forKey: key.description) } }
3. UserDefaultKey
を使ったUserDefaultのデータストアの実装
protocol UserDefaultsDataStore { var autoReloadEnabled: String? { get set } } fileprivate enum UserDefaultsDataStoreKeys: String, UserDefaultKey { case autoReloadEnabled } struct UserDefaultsDataStoreImpl: UserDefaultsDataStore { var autoReloadEnabled: Bool { get { return bool(forKey: .autoReloadEnabled) ?? false } set { set(value: newValue, forKey: .authToken) } } private var defaults: UserDefaults { return UserDefaults.standard } } private extension UserDefaultsDataStoreImpl { func object(forKey key: UserDefaultsDataStoreKeys) -> Any? { return defaults.object(forKey: key) } func url(forKey key: UserDefaultsDataStoreKeys) ->URL? { return defaults.url(forKey: key) } func array(forKey key: UserDefaultsDataStoreKeys) ->[Any]? { return defaults.array(forKey: key) } func dictionary(forKey key: UserDefaultsDataStoreKeys) ->[String: Any]? { return defaults.dictionary(forKey: key) } func string(forKey key: UserDefaultsDataStoreKeys) ->String? { return defaults.string(forKey: key) } func stringArray(forKey key: UserDefaultsDataStoreKeys) ->[String]? { return defaults.stringArray(forKey: key) } func data(forKey key: UserDefaultsDataStoreKeys) ->Data? { return defaults.data(forKey: key) } func bool(forKey key: UserDefaultsDataStoreKeys) ->Bool? { return defaults.bool(forKey: key) } func integer(forKey key: UserDefaultsDataStoreKeys) ->Int? { return defaults.integer(forKey: key) } func float(forKey key: UserDefaultsDataStoreKeys) ->Float? { return defaults.float(forKey: key) } func double(forKey key: UserDefaultsDataStoreKeys) ->Double? { return defaults.double(forKey: key) } func set<V>(value: V?, forKey key: UserDefaultsDataStoreKeys) { if let v = value { defaults.set(v, forKey: key) defaults.synchronize() } } }
DataStore側でもgetterを定義していて若干冗長ですが、.autoReloadEnabled
のようにEnumを省略形で書くためそうしています。
今回はUserDefaultKey
というprotocolを用意しましたが
もし、enumは1個しか定義しないと仮定するならば
具象enumを引数に取るUserDefaultsのextensionを書けばいいのでもっと簡潔にはできると思います。