アトラシエの開発ブログ

株式会社アトラシエのブログです

NSObjectを継承した独自クラスをNSUserDefaultsに保存する[Swift]

iPhoneアプリで手軽にローカルにデータを保存する際にNSUserDefaultsが重宝します。

ただ、通常の方法だと単純なデータ(文字列、Dictionaryなど)を格納することはできるのですが、NSObjectを継承して作ったような独自クラスの保存はできません。 NSUserDefaultsで扱えるようにするにはNSCodingというプロトコルに応答することと、それをアーカイブして保存する2つの工程が必要です。

1. NSCodingを実装する

import Foundation
import SwiftyJSON

class PhotoModel: NSObject, NSCoding {
    var id : Int?
    var picture : Dictionary<String, AnyObject>?
    
    class func photosFromJSON(list : Array<JSON>) -> [PhotoModel] {
        var array = [PhotoModel]()
        for item in list {
            let photo = PhotoModel()
            photo.assignAttributes(item)
            array.append(photo)
        }
        return array
    }
    
    func assignAttributes(params : JSON) {
        self.id = params["id"].intValue
        self.picture = params["picture"].dictionaryObject
    }
    
    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeInteger(id!, forKey: "id")
        aCoder.encodeObject(picture, forKey: "picture")
    }
    
    required init(coder aDecoder: NSCoder) {
        id = aDecoder.decodeIntegerForKey("id")
        picture = aDecoder.decodeObjectForKey("picture") as? [String : AnyObject]
        super.init()
    }
    
    override init() {
        super.init()
    }
}

これはAPIサーバから受け取ったJSONを処理してモデルを表現する簡単なクラスですが、encodeWithCoder(aCoder: NSCoder)init(coder aDecoder: NSCoder)の2つを実装すればいいわけです。

ただ、init(coder aDecoder: NSCoder)の他にinit()も残しておいたほうがPhotoModel()のような単純なイニシャライズができるので書いておきましょう。

2. NSKeyedArchiverで保存、NSKeyedUnarchiverで取り出す

次にエンコード可能なオブジェクトをアーカイブしてNSData型にして格納します。ご覧のように格納、受け取るオブジェクト自体はArray型([PhotoModel])ですが、userDefaultsで扱う際にはNSDataなのでarrayForKey:でなくobjectForKeyでOKです。

    func userDefault() -> (NSUserDefaults) {
        return NSUserDefaults.standardUserDefaults()
    }

    func setDraftPhotos(photos : [PhotoModel]) {
        let archive = NSKeyedArchiver.archivedDataWithRootObject(photos)
        userDefault().setObject(archive, forKey: "draftPhotos")
    }

    func draftPhotos() -> ([PhotoModel]) {
        if let data = userDefault().objectForKey("draftPhotos") as? NSData {
            let unarchive = NSKeyedUnarchiver.unarchiveObjectWithData(data)
            return unarchive as! [PhotoModel]
        } else {
            return [PhotoModel]()
        }
    }

まとめ

これはあくまでクライアント型で2,3個のデータを保存したい際に便利な手法です。 もっと大規模に保存するならCoreDataの使用を検討しましょう。

また、モデル自体を保存するよりもid配列を保存して実行のたびにサーバからロードするほうがいい場合もあるのでサービスの要件と相談すべきです。