LoginSignup
17
17

More than 5 years have passed since last update.

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

Posted at

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

SwiftDefaultsでできること

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

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のデフォルト値になります。

import SwiftDefaults

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

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

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

実装内容

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

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の値をセットしています。

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に値を保存するようにしています。

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に渡すことでデフォルト値をセットしています。

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のようにラップするクラスを作るのが良さそうな気がしてます。

import SwiftDefaults

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
17