NSUserDefaultsにプロパティー形式でアクセスできるライブラリを作ってみた

NSUserDefaultsへのアクセスを簡単にしてくれるSwiftDefaultsというライブラリを作成しました。
SwiftDefaultsという名前のライブラリです。

https://github.com/shimesaba9/SwiftDefaults

## SwiftDefaultsでできること
表題の通りNSUserDefaultsにプロパティー形式でアクセスできるようになります。
文字列でキーを指定する必要がなくなるのでTypoの心配もありません。

```swift
import SwiftDefaults

class MyDefaults: SwiftDefaults {
    dynamic var value: String? = "10"
    dynamic var value2: String = "10"
    dynamic var value3: Int = 1
}

// NSUserDefaultsの値の取得
print(MyDefaults().value) // "10"
print(MyDefaults().value2) // "10"
// NSUserDefaultsへの保存
MyDefaults().value2 = "2"
print(MyDefaults().value2) // "2"
print(MyDefaults().value3) // 1
```

プロパティーに与えた初期値がNSUserDefaultsのデフォルト値になります。

```swift
import SwiftDefaults

class MyDefaults: SwiftDefaults {
    dynamic var value: String? = "10" // valueのデフォルト値は10になる
}
```

もしこのライブラリを使わない場合は上で挙げた事を実現する為に以下のように書く必要があります。(実際はラッパークラスを書いてもう少し見やすくすると思いますが)

```swift
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.registerDefaults([
    "value": "10",
    "value2": "10",
    "value3": 1
    ])
print(userDefaults.stringForKey("value"))
print(userDefaults.stringForKey("value2")!)
userDefaults.setObject("2", forKey: "value2")
userDefaults.synchronize()
print(userDefaults.stringForKey("value2")!)
print(userDefaults.integerForKey("value3"))
```

## 実装内容
短いライブラリなので実装方法も軽く触れてみます。
ライブラリ本体のコードは以下の通りです。

```swift
import Foundation

public class SwiftDefaults: NSObject {
    let userDefaults = NSUserDefaults.standardUserDefaults()
    
    public override init() {
        super.init()
        
        registerDefaults()
        setupProperty()
        addObserver()
    }
    
    deinit {
        removeObserver()
    }
}

extension SwiftDefaults {
    override public func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        
        if let keyPath = keyPath {
            userDefaults.setObject(change?["new"], forKey: keyPath)
            userDefaults.synchronize()
        }
    }
}

extension SwiftDefaults {
    private func registerDefaults() {
        let dic = propertyNames.reduce([String:AnyObject]()) { (var dic, key) -> [String:AnyObject] in
            dic[key] = valueForKey(key)
            return dic
        }
        userDefaults.registerDefaults(dic)
    }
    
    private func setupProperty() {
        propertyNames.forEach {
            setValue(userDefaults.objectForKey($0), forKey: $0)
        }
    }
    
    private func addObserver() {
        propertyNames.forEach {
            addObserver(self, forKeyPath: $0, options: .New, context: nil)
        }
    }
    
    private func removeObserver() {
        propertyNames.forEach {
            removeObserver(self, forKeyPath: $0)
        }
    }
    
    private var propertyNames: [String] {
        return Mirror(reflecting: self).children.flatMap { $0.label }
    }
}
```

## NSUserDefaultsの値を取得する仕組み
「MyDefaults().value」という形式でNSUserDefaultsの値を取得できる仕組みですが、ここは最初に値をセットする事で実現しています。
具体的にはSwiftDefaultsの`setupProperty`メソッド内で各プロパティーにNSUserDefaultsの値を詰め込んでいます。

Mirrorオブジェクトを使えばSwiftDefaultsのプロパティー一覧を取得できるので、それを使ってプロパティー1つ1つにNSUserDefaultsの値をセットしています。

```swift
private var propertyNames: [String] {
    return Mirror(reflecting: self).children.flatMap { $0.label }
}

private func setupProperty() {
    propertyNames.forEach {
        setValue(userDefaults.objectForKey($0), forKey: $0)
    }
}
```

## NSUserDefaultsに値を保存する仕組み
値の保存はKVOを使って実現しています。
具体的な実装箇所としてはSwiftDefaultsの`addObserver`メソッドと`observeValueForKeyPath`メソッドです。

KVOを使えばプロパティーへの値の代入時に`observeValueForKeyPath`メソッドが呼ばれるようになります。
それを利用して`observeValueForKeyPath`メソッド内でNSUserDefaultsに値を保存するようにしています。

```swift
private func addObserver() {
    propertyNames.forEach {
        addObserver(self, forKeyPath: $0, options: .New, context: nil)
    }
}

override public func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
        
    if let keyPath = keyPath {
        userDefaults.setObject(change?["new"], forKey: keyPath)
        userDefaults.synchronize()
    }
}
```

## NSUserDefaultsのデフォルト値を登録する仕組み
NSUserDefaultsのデフォルト値の登録は`registerDefaults`メソッド内で行っています。
ここでもプロパティー名一覧を使ってDictionaryを作成、それを`userDefaults.registerDefaults`に渡すことでデフォルト値をセットしています。

```swift
private func registerDefaults() {
    let dic = propertyNames.reduce([String:AnyObject]()) { (var dic, key) -> [String:AnyObject] in
        dic[key] = valueForKey(key)
        return dic
    }
    userDefaults.registerDefaults(dic)
}
```

## 今後対応したい事
Int?やBool?と言ったObjective-cでプリミティブなクラスのOptional型への対応をしたいと思います。
現状SwiftDefaultsではdynamicを使っているのでObjective-cでプリミティブだったクラスのOptional型を扱えません。
dynamicはKVOを使う為に付けてます。

対応方法としてはRealmのRealmOptionalのようにラップするクラスを作るのが良さそうな気がしてます。

```swift
import SwiftDefaults

class MyDefaults: SwiftDefaults {
    dynamic var value: Int? = 1 // これはエラーになる
}
```