NSUserDefaultsのためにNSDictionaryの中身を綺麗にしたい

iOSのアプリを作っていると,アプリのデータを保存するときにNSUserDefaultsを使うことがあると思う.

ローカルに保存するし,DBを導入するほどではないときに非常に便利に使わせてもらっている.


しかし,NSUserDefaultsには少し制限があって,たとえば保存しておける型に縛りがあったりする.

https://developer.apple.com/library/prerelease/iOS/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/index.html



もちろんNSDictionaryは保存できるのだが,内部にNSNullを含むNSDictionaryを保存しようとすると,

saving dictionary error Property list invalid for format (property lists cannot contain objects of type 'CFNull')

こんなエラーを吐かれる.


で,これは言われている通りで,CFNullのままじゃ保存できないんですね.

じゃぁNullのところを別のものに書き換えればいい


なんでもいいんですけど,DictionaryだしString型の""に書き換えてみます.

func cleanDictionary(dict: NSMutableDictionary)->NSMutableDictionary {
    var mutableDict: NSMutableDictionary = dict.mutableCopy() as NSMutableDictionary
    mutableDict.enumerateKeysAndObjectsUsingBlock { (key, obj, stop) -> Void in
        if (obj.isKindOfClass(NSNull.classForCoder())) {
            mutableDict.setObject("", forKey: (key as NSString))
        } else if (obj.isKindOfClass(NSDictionary.classForCoder())) {
            mutableDict.setObject(self.cleanDictionary(obj as NSMutableDictionary), forKey: (key as NSString))
        }
    }
    return mutableDict
}

var userDictionary = cleanDictionary(dictionary as NSMutableDictionary)
var userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setObject(userDictionary, forKey: "dictionary")


こんな感じの関数を作ります.
で,ここにNSMutableDictionary型に変換したDictionaryを突っ込んであげると,見事NSNull型をString型の""に変更してきます.
ちなみに,ちゃんと再帰処理させてあるので,Dictionaryの中にDictionaryを突っ込む木構造でも問題なくすべてNSNullを掃除してくれます.


二行目で受け取ったdictをいきなりmutableCopyしています.
受け取っている時点で,型指定がNSMutableDictionaryなので大丈夫そうな気がするんですが,これを挟まずにやると,setObjectメソッドを走らせた時に,

reason: '-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object'

と怒られます.
そのため対処療法ですが,強制的に変換しています.





多分これ,NSUserDefaults以外にもNSFileManagerなどでも使う場面があるかと思うので,押さえておくと便利です.