[Swift 4~] UserDefaults を Observable で Testable なものにしたい！

みなさん`UserDefaults`使ってますか?
設定情報などを永続保持するのに便利ですよね :thumbsup:

今回は, そんな`UserDefaults`をちょっと幸せにするTipsをご紹介します.

# Swiftらしく, Observableで, TestableなUserDefaults

**Swiftらしく** というと違和感を覚える方もいるかもしれませんが, 要は `Protocol` や `Enum` を駆使して安全で扱いやすくするということです.

`UserDefaults` は *KVO* による値の監視が可能です. しかし, *Swift 4* で追加された `observe(_:options:changeHandler:)` は `UserDefaults` では使えません. 同じようにクロージャで *KVO* を実装できるようにしたいですよね.

`UserDefaults` を使用する関数のテストはどのようにしていますか? とりあえず現在の値を別で保持して, テスト値を入れてテスト, 最後に保持した値で元に戻す, なんてことをしていませんか? 面倒な上にミスが起きやすいですよね. 実際に扱うデータに影響を与えず, テスト値を入れたり値を監視したりするにはどうすればよいでしょうか.

この記事では, これらをどうやって実現/解決するか, 私が実践してる方法を紹介します.

# Swiftらしく

`Protocol` や `Enum` を用いて `UserDefaults` のキーを管理する方法は数多く紹介されています.
私は, それらの中でもよく見かける `KeyNamespaceable` 的なやつを採用しています. 詳細は割愛しますので, 詳しくは「UserDefaults　KeyNamespaceable」などで調べてみてください.

```swift:KeyNamespaceable.swift
protocol KeyNamespaceable {}

extension KeyNamespaceable {
    static func namespace<T: RawRepresentable>(_ key: T) -> String where T.RawValue == String {
        return "\(Self.self)_\(T.self)_\(key.rawValue)"
    }
}

protocol ObjectUserDefaultable: KeyNamespaceable {
    associatedtype ObjectDefaultKey: RawRepresentable
}

extension ObjectUserDefaultable where ObjectDefaultKey.RawValue == String {
    static func set(_ value: Any?, forKey key: ObjectDefaultKey) {
        UserDefaults.standard.set(value, forKey: namespace(key))
    }

    static func object(for key: ObjectDefaultKey) -> Any? {
        return UserDefaults.standard.object(forKey: namespace(key))
    }

    static func remove(for key: ObjectDefaultKey) {
        UserDefaults.standard.removeObject(forKey: namespace(key))
    }
}

public protocol IntegerUserDefaultable: KeyNamespaceable { /*同様の実装*/ }
...
...
```

```swift:使用例
struct MyUserDefaults: ObjectUserDefaultable, BoolUserDefaultable {
    enum ObjectDefaultKey: String {
        case userName
        case appTheme
    }

    enum BoolDefaultKey: String {
        case needsBackup
    }
}

MyUserDefaults.set("krimpedance", forKey: .userName)
let needsBackup = MyUserDefaults.bool(forKey: .needsBackup)
```

*Enum* でアクセスすることで, タイポを防ぎつつ, Xcodeの補完が効くのでスマートになります. :v_tone2:
よく `UserDefaults` に直接上記 *Protocol* 群を継承させている例もありますが, *Testable* にすることなど考えて, 独自の構造体を作っています.

# Observable化

冒頭でも触れましたが, `UserDefaults` は `NSObject` のサブクラスなので, *KVO* により値を監視することができます.
これで良いといえば良いのですが, *Swift 4* で `KeyPath`[^1] が追加されたことで, クロージャ式の *KVO* メソッドも追加されました. *SwiftLint* さんにも怒られるし[^2], こっち使いたいですよね.

ただこの `KeyPath`, 使えるのは `NSObject` を継承したクラスの `@objc dynamic var` メンバのみなんです. つまり, **`UserDefaults`では使えません**. :innocent:

> *仲間外れは「ダメ。絶対。」*

ということで, [NSObjectのSwift拡張](https://github.com/apple/swift/blob/master/stdlib/public/SDK/Foundation/NSObject.swift)を参考に `UserDefaults`用の関数を実装したいと思います.

### クロージャ式 *KVO*

まず, *Swift 4* で追加されたクロージャ式の `ovserve` 関数を見てみましょう.

```swift:[apple/swift]NSObject.swift
public func observe<Value>(_ keyPath: KeyPath<Self, Value>, options: NSKeyValueObservingOptions = [], changeHandler: @escaping (Self, NSKeyValueObservedChange<Value>) -> Void) -> NSKeyValueObservation {
    let result = NSKeyValueObservation(object: self as! NSObject, keyPath: keyPath) { (obj, change) in
        let notification = NSKeyValueObservedChange(kind: change.kind, newValue: change.newValue as? Value, oldValue: change.oldValue as? Value, indexes: change.indexes, isPrior: change.isPrior)
        changeHandler(obj as! Self, notification)
    }
    result.start(options)
    return result
}
```

```swift:使用例
class Hoge {
    let scrollView = UIScrollView()
    var observation: NSKeyValueObservation?
    
    setUp() {
        observation = scrollView.observe(\.contentOffset) { view, change in
            print("move to (x: \(change.newValue!.x), y: \(change.newValue!.y))")
        }
    }

    tearDown() {
        observation.invalidate()
        // もしくは
        // observation = nil
        // もしくは自身(Hoge)の解放
    }
}
```

これまでの `addObserver(...)` を `NSKeyValueObservation` クラスで代行して, 内部で変形, クロージャを実行しているようです. そして, 返り値でこのクラスのオブジェクトを返しているため, 「オブジェクトの生存期間 = `KayPath` の監視期間」となり, 扱いやすくなってます.

`KeyPath` はメンバ名とその型情報がわかるようになっているので, これを `Value.Type` と `String` に分ければなんとかなりそうです. :sunglasses: 

### UserDefault用NSKeyValueObservationクラスの作成

`NSKeyValueObservation` を簡易コピーして `UserDefaults` 用の `UDKeyValueObservation` クラスを作ります. イニシャライザが *internal* なので, 同じように`UDKeyValueObservedChange`構造体も作る必要があります.

(以降のコードは省略部分も多数あります. 完全版は一番最後にリンクがあります. )

```swift:UDKeyValueObservation.swift
struct UDKeyValueObservedChange<Value> {
    typealias Kind = NSKeyValueChange
    let kind: Kind
    let newValue: Value?
    let oldValue: Value?
    let indexes: IndexSet?
    let isPrior:Bool
}

class UDKeyValueObservation: NSObject {
    // 循環参照を避けるために監視オブジェクトは弱参照
    weak var object: NSObject?
    // 値変更時に実行するクロージャ(初期化時のクロージャ)
    let callback: (NSObject, UDKeyValueObservedChange<Any>) -> Void
    // 監視するUserDefaultsのキー
    let defaultName: String

    // 本家では KeyPath → String 変換が行われる
    fileprivate init(object: NSObject, defaultName: String, callback: @escaping (NSObject, UDKeyValueObservedChange<Any>) -> Void) {
        self.object = object
        self.defaultName = defaultName
        self.callback = callback
    }

    // 値の変更はまずここに通知される
    // 本家では, 黒魔術(method_exchangeImplementations)で独自関数と入れ替えてる
    override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey: Any]?, context: UnsafeMutableRawPointer?) {
        guard
            let ourObject = self.object,
            let change = change,
            object as? NSObject == ourObject,
            keyPath == defaultName
        else { return }

        // [NSKeyValueChangeKey: Any] => UDKeyValueObservedChange
        let rawKind = change[.kindKey] as! UInt
        let kind = NSKeyValueChange(rawValue: rawKind)!
        let notification = UDKeyValueObservedChange(kind: kind,
                                          newValue: change[.newKey],
                                          oldValue: change[.oldKey],
                                          indexes: change[.indexesKey] as! IndexSet?,
                                          isPrior: change[.notificationIsPriorKey] as? Bool ?? false)
        // クロージャを実行
        callback(ourObject, notification)
    }

    // 監視を開始
    // イニシャライザで行わないのは, options　に .initial が含まれていた場合, 自身の初期化前に通知されてしまうため（だと思われる）.
    fileprivate func start(_ options: NSKeyValueObservingOptions) {
        object?.addObserver(self, forKeyPath: defaultName, options: options, context: nil)
    }

    // 監視を解除.
    // deinit 時に自動で呼ばれると言いつつそれぞれ解除処理してる.
    func invalidate() {
        object?.removeObserver(self, forKeyPath: defaultName, context: nil)
        object = nil
    }

    deinit {
        object?.removeObserver(self, forKeyPath: defaultName, context: nil)
    }
}
```

よく意図がわからない実装を飛ばしつつも, ほぼコピペで `NSKeyValueObservation` の `KeyPath` を `String` に置き換えることができました.
次は, 実際に使う側から実行する `observe` 関数を実装します.

```swift:UIKeyValueObservation.swift
typealias UDChangeHandler<O, V> = (O, UDKeyValueObservedChange<V>) -> Void

// KeyPathの方は _KeyValueCodingAndObserving プロトコルが定義されている
protocol UDKeyValueCodingAndObserving {}

extension UDKeyValueCodingAndObserving {
    func observe<Value>(_ type: Value.Type, forKey defaultName: String, options: NSKeyValueObservingOptions = [], changeHandler: @escaping UDChangeHandler<Self, Value>) -> UDKeyValueObservation {
        let result = UDKeyValueObservation(object: self as! NSObject, defaultName: defaultName) { obj, change in
            let notification = UDKeyValueObservedChange(kind: change.kind, newValue: change.newValue as? Value, oldValue: change.oldValue as? Value, indexes: change.indexes, isPrior: change.isPrior)
            changeHandler(obj as! Self, notification)
        }
        result.start(options)
        return result
    }
}

extension UserDefaults: UDKeyValueCodingAndObserving {}
```

こちらもいい感じに実装できました. ちゃんと動くか確認してみましょう.

```swift:確認
class Hoge {
    var observation: UDKeyValueObservation?

    init() {
        observation = UserDefaults.standard.observe(String.self, forKey: "key-1", options: [.new, .old]) { _, change in
            print(change)
        }
    }
}

var hoge = Hoge()
print("1")
UserDefaults.standard.set("hoge", forKey: "key-1")
print("2")
UserDefaults.standard.set("hoge", forKey: "key-1")
print("3")
UserDefaults.standard.set("fuga", forKey: "key-1")
print("4")
UserDefaults.standard.set(nil, forKey: "key-2")
print("5")
hoge.observation = nil
UserDefaults.standard.set("hoge", forKey: "key-1")

// 1
// Hoge Optional("hoge") nil
// Hoge Optional("hoge") nil
// 2
// 3
// Hoge Optional("fuga") Optional("hoge")
// Hoge Optional("fuga") Optional("hoge")
// 4
// Hoge nil Optional("fuga")
// Hoge nil Optional("fuga")
// 5
```

はい, できてる !! [^3]

どうしました ?
この挙動, **正しい** ですよね ? :innocent: 

正しい, というのは想定される挙動という意味で, 2回通知が来ることではありません. ご存知の方も多いと思いますが, 実はこれ未だに直っていないバグなんです. [こちらの記事](https://qiita.com/salexkidd@github/items/743c4ae769812bf1b056)に最新の情報とともに書かれています.
バグならしょうがない, 2回飛んでくるだけだ, じゃ済まない状況もあります. :joy:
なのでとりあえずは, [こちらの方法](https://stackoverflow.com/a/36398468)などで抑制[^4]するしかなさそうです.

### KeyNamespaceable 対応

`String` でクロージャ式 *KVO* ができるようになったので, 最初の `KeyNamespaceable` でも対応しましょう.

```swift:KeyNamespaceable.swift
extension ObjectUserDefaultable {
    static func ovserve<Value>(_ type: Value.Type, forKey key: ObjectDefaultKey, changeHandler: @escaping UDChangeHandler<UserDefaults, Value>) -> UDKeyValueObservation {
        let key = namespace(key)
        return UserDefaults.standard.observe(type, forKey: key, changeHandler: changeHandler)
    }
}

extension IntegerUserDefaultable { /* 同様の実装 */ }
...
...
```

```swift:使用例
let observation = MyUserDefaults.observe(String.self, forKey: .userName) { defaults, change in
    print(change)
}
// 型が固定のキーは上記の第一引数は無くても良い
let observation2 = MyUserDefaults.observe(forKey: .needsBackup) { defaults, change in
    print(change)
}
```

いい感じですね. これで `UserDefaults` もクロージャ式 *KVO* の仲間入りです.

### Plus Ultra

一つのクラスで複数の *KVO* 監視を行うとき, `NSKeyValueObservation` オブジェクトは配列で保持することがあります. 今回は, `UDKeyValueObservation` という別のクラスを作成したため, 配列を2つ用意することになります. これを回避するために *Plotocol* を用いて一つの配列で管理できるようにするのが良さそうです.

```swift:KeyValueObservatationable.swift
protocol KeyValueObservationable {
    // 外から見えるのはこれだけでよい
    func invalidate()
}

extension NSKeyValueObservation: KeyValueObservationable {}
extension UDKeyValueObservation: KeyValueObservationable {}

// 使用例
var observations = [KeyValueObservationable]()
let observationNS = scrollView.observe(\.contentOffset) { ... }
let observationUD = userDefaults.observe("userName") { ... }
observations.append(contentsOf: [observationNS, observationUD])
```

# Testable化

実際に扱う値や他のテストに影響を与えずに, `UserDefaults`を操作するためには, 以下のようにする必要があります.

```swift
class Hoge {
    let defaults: UserDefaultable

    init(defaults: UserDefaultable) {
        self.defaults = defaults
    }

    func save(value: Int) {
        defaults.set(value, forKey: "key")
    }
}

// 通常時
let hoge = Hoge(defaults: UserDefaults.standard)
// テスト時
let hoge = Hoge(defaults: UserDefaultsFake())
```

テスト駆動開発などでよく見ますね.
共通の *Protocol* ( `UserDefaultable` ) を用意し, 仮データを入れたり, 書込/読込を監視したりするクラス( `UserDefaultsFake` )を作成し, 依存性注入( `init(defaults:)` )により対象を切り替えます.

### Protocol の準備

余計な実装を省くために, 関数名などは `UserDefaults` に合わせます.

```swift:UserDefaultable.swift
public protocol UserDefaultable: UDKeyValueCodingAndObserving {
    func object(forKey defaultName: String) -> Any?
    func string(forKey defaultName: String) -> String?
    ...

    func set(_ value: Any?, forKey defaultName: String)
    func set(_ value: Int, forKey defaultName: String)
    ...

    func removeObject(forKey defaultName: String)

    // これだけ自前で用意
    func reset()
}
```

途中省略していますが, 取得/保存用の関数があれば十分でしょう. `UserDefaults` の初期化だけは面倒なので, 独自で用意しています. また, 先ほどの `UDKeyValueCodingAndObserving` も継承させています.

さて, それではまずこの *Protocol* を `UserDefaults` に継承させます. 関数名を合わせているので, 余計な実装は必要ありません.

```swift:UserDefaultable.swift
extension UserDefaults: UserDefaultable {
    public func reset() {
        guard let bundleId = Bundle.main.bundleIdentifier else { return }
        removePersistentDomain(forName: bundleId)
    }
}
```

### *Fake*クラスの作成

テスト用に, `UserDefaults` の *Fake* クラスを作ります.

```swift:UserDefaultsFake.swift
public class UserDefaultsFake: UserDefaultable {
    private var values = [String: Any]() // 値格納用

    // MARK: Getter --------------

    func object(forKey defaultName: String) -> Any? {
        return values[defaultName]
    }

    func string(forKey defaultName: String) -> String? {
        return values[defaultName] as? String
    }

    ...

    // MARK: Setter --------------

    func set(_ value: Any?, forKey defaultName: String) {
        guard let val = value else { removeObject(forKey: defaultName); return }
        values[defaultName] = val
    }

    func set(_ value: Int, forKey defaultName: String) {
        values[defaultName] = value
    }

    ...

    func reset() {
        values = [:]
    }
}
```

実にシンプルな実装です. ただ, このままだと *KVO* で値を監視できませんね.
*KVO* で値を監視するための修正を加えましょう.

```diff:UserDefaultsFake.swift
- public class UserDefaultsFake: UserDefaultable {
+ public class UserDefaultsFake: NSObject, UserDefaultable {
...
      func set(_ value: Int, forKey defaultName: String) {
+         willChangeValue(forKey: defaultName)
          values[defaultName] = value
+         didChangeValue(forKey: defaultName)
      }
...
+     override public func value(forKey key: String) -> Any? {
+         return values[key]
+     }
...
```

まず, 基本として `NSObject` のサブクラスにします.
`willChangeValue`, `didChangeValue` はセットで呼ぶことで, 自身を `addObserver` したクラスの `obsreveValue` 関数がトリガーされます.
ただ, これだけだと唯一のメンバである `values` しか監視できません. そのため, `value(forKey:)` をオーバーライドして, `values` の各キーを見るように書き換えてあげます. [^5]

### KeyNamespaceable 対応

*Observable* の時と同様, `KeyNamespaceable` も *Testable* に対応させます.

```diff:KeyNamespaceable.swift
-     static func set(_ value: Any?, forKey key: ObjectDefaultKey) {
-         UserDefaults.standard.set(value, forKey: namespace(key))
+     static func set(_ value: Any?, forKey key: ObjectDefaultKey, source: UserDefaultable = UserDefaults.standard) {
+         source.set(value, forKey: namespace(key))
      }

      static func observe<Value>(_ type: Value.Type,
                                 forKey key: ObjectDefaultKey,
+                                source: UserDefaultable = UserDefaults.standard,
                                 options: NSKeyValueObservingOptions = [],
-                                changeHandler: @escaping UDChangeHandler<UserDefaults, Value>) -> UDKeyValueObservation {
-         return UserDefaults.standard.observe(type, forKey: namespace(key), options: options, changeHandler: changeHandler)
+                                changeHandler: @escaping UDChangeHandler<UserDefaultable, Value>) -> UDKeyValueObservation {
+         return source.observe(type, forKey: namespace(key), options: options, changeHandler: changeHandler)
    }
```

`source: UserDefaultable` という引数をそれぞれ追加しました. これも依存性注入ですね. 自家製の小さいアプリや, プロジェクトによってはテストを書かないこともあるので, デフォルト値に `UserDefaults.standard` を設定してみました.

```swift
class Hoge {
    let defaults: UserDefaultable

    init(defaults: UserDefaultable) {
        self.defaults = defaults
    }

    func save(value: Int) {
        MyUserDefaults.set(value, forKey: .hogeKey, source: defaults)
    }
}
```

良さそうですね.

不完全燃焼の部分もありますが, これで **Swiftらしく, Observableで, TestableなUserDefaults** の完成です.

# 完成物

[Gist](https://gist.github.com/krimpedance/27a2e3c5a7d301b4b468cca944279357)にまとめました. *Playground* にコピペして試してみてください.




[^1]: 参考Qiita記事: [#keyPathとKeyPath](https://qiita.com/_tid_/items/6d44d0e91d2d02d0cfaa)
[^2]: `block_based_kvo` ルールにより, *Warning* が出るようになりました.
[^3]: https://www.youtube.com/watch?v=j0JBvkJu1_U. ご冥福をお祈りいたします.
[^4]: あくまで **抑制** です. タイミングによっては2回飛んでくることもありました.
[^5]: この辺は試行錯誤した末に偶然見つけた方法で, 正しい方法かは自信がありません. 詳しい方がいたら補足していただきたいです. :bow_tone1: