読者です 読者をやめる 読者になる 読者になる

WonderPlanet DEVELOPER BLOG

ワンダープラネットの開発者ブログです。モバイルゲーム開発情報を発信。

Swiftでのデータ永続化

今回のエンジニアブログを担当する村田です。

本日は、Swiftではデータ永続化の記述はどうなるか
まとめてみました。

確認バージョン:Xcode 6 beta 4, beta 5

Objective-Cには、簡単なデータ永続化として

  • NSKeyedArchiverを使ったオブジェクトのアーカイブ
  • プロパティリストを使ったデータ保存
  • NSUserDefaultsを使ったデータ保存

があります。
もちろんSwiftでも健在です。

「Core Data」や「SQLite」を使った方法もありますが、
お手軽なデータ永続化としては上記の方法ではないでしょうか。

それぞれ、Swiftではどのような記述になるでしょうか。
でもその前に...

パスの取得方法

プログラムから操作するパスとして、以下の3つがあります。

  • /Documents
    アプリがファイルを作成して保存できる領域
  • /Library/Caches
    アプリが一時的に使用する領域
  • /tmp
    一時ファイルの保存先に使用する領域

まず、Objective-Cの例です。

// /Documentsまでのパス取得方法  
NSArray *paths1 = NSSearchPathForDirectoriesInDomains(  
                                NSDocumentDirectory,   
                                NSUserDomainMask, YES);  
NSString *documentsPath = [paths1 objectAtIndex:0];  
  
// /Library/Cachesまでのパス取得方法  
NSArray *paths2 = NSSearchPathForDirectoriesInDomains(  
                                NSCachesDirectory,   
                                NSUserDomainMask, YES);  
NSString *cachesPath = [paths2 objectAtIndex:0];  
  
// /tmpまでのパス取得方法  
NSString *tmpPath = NSTemporaryDirectory();  

Swiftではこうなります。

// /Documentsまでのパス取得方法  
let paths1 = NSSearchPathForDirectoriesInDomains(  
                                .DocumentDirectory,  
                                .UserDomainMask, true)  
let documentsPath = paths1[0]  
  
// /Library/Cachesまでのパス取得方法  
let paths2 = NSSearchPathForDirectoriesInDomains(  
                                .CachesDirectory,  
                                .UserDomainMask, true)  
let cachesPath = paths2[0]  
  
// /tmpまでのパス取得方法  
let tmp = NSTemporaryDirectory()  

NSSearchPathForDirectoriesInDomains関数も健在です。

  • 第一引数
    enum型であるNSSearchPathDirectoryの値
  • 第二引数
    struct型であるNSSearchPathDomainMaskの値

上の記述は省略した記述になりますが、省略せず記述すると下のようになります。

// /Documentsまでのパス取得方法  
let paths1 = NSSearchPathForDirectoriesInDomains(  
                                NSSearchPathDirectory.DocumentDirectory,  
                                NSSearchPathDomainMask.UserDomainMask, true)  

取得したパスにファイル名を追加したい場合は、StringのstringByAppendingPathComponent関数を使います。

// /Documentsまでのパス取得  
let paths1 = NSSearchPathForDirectoriesInDomains(  
                                .DocumentDirectory,  
                                .UserDomainMask, true)  
// /Documentsまでのパスにファイル名"sample.dat"を付与  
let path = paths1[0].stringByAppendingPathComponent("sample.dat")  

以降のサンプルソースで、_pathという変数が出てきますが、
保存ファイルまでのパスが設定されていると思って下さい。

NSKeyedArchiverを使ったオブジェクトのアーカイブ

例として、Dictionary型の値をNSKeyedArchiverでアーカイブしてみます。

// 保存するデータ  
var user = [  
    "Name": "名古屋太郎",  
    "Address": "名古屋市中区",  
    "Tel": "052-xxx-xxxx",  
]  
// NSKeyedArchiverクラスを使ってデータを保存する。  
// 第一引数に保存するデータ、第二引数にファイルパスを渡します。  
let success = NSKeyedArchiver.archiveRootObject(user, toFile: _path)  
  
if success {  
    println("保存に成功")  
}  
// NSKeyedUnarchiverクラスを使って保存したデータを読み込む。  
let user = NSKeyedUnarchiver.unarchiveObjectWithFile(_path) as [String: String]  
  
for (key, value) in user {  
    println("\(key)\(value)")  
}  

Objective-Cの時と同じクラス、同じ関数が使用できます。

  • アーカイブ時
    NSKeyedArchiverクラスのarchiveRootObject
  • アンアーカイブ
    NSKeyedUnarchiverクラスのunarchiveObjectWithFile

NSObjectクラスを継承し、NSCodingプロトコルを実装すれば自作したクラスもアーカイブ可能です。

class SampleUser: NSObject, NSCoding {  
    var id: Int!  
    var name : String!  
  
    init(id: Int, name: String)  {  
        super.init()  
  
        self.id = id  
        self.name = name  
    }  
    // MARK: - Implements NSCoding protocol  
    func encodeWithCoder(aCoder: NSCoder!) {  
        // Int型のエンコード  
        aCoder.encodeInteger(self.id, forKey: "ID")  
        // String型のエンコード  
        aCoder.encodeObject(self.name, forKey: "NAME")  
    }  
  
    init(coder aDecoder: NSCoder!) {  
        super.init()  
        // Int型のデコード  
        self.id = aDecoder.decodeIntegerForKey("ID")  
        // String型のデコード  
        self.name = aDecoder.decodeObjectForKey("NAME") as String  
    }  
  
}  
// 保存するデータ  
var users = [  
    SampleUser(id: 1, name: "Aichi"),  
    SampleUser(id: 2, name: "Gifu"),  
    SampleUser(id: 3, name: "Mie"),  
]  
// NSKeyedArchiverクラスを使ってデータを保存する。  
let success = NSKeyedArchiver.archiveRootObject(users, toFile: _path)  
  
if success {  
    println("保存に成功")  
}  
// NSKeyedUnarchiverクラスを使って保存したデータを読み込む。  
let users = NSKeyedUnarchiver.unarchiveObjectWithFile(path) as [SampleUser]  
  
for user in users {  
    println("ID: \(user.id), NAME: \(user.name)")  
}  

プロパティリストを使ったデータ保存

例として、NSDictionary型の値をプロパティリストで保存してみます。

// 保存するデータ  
var user: NSDictionary = [  
    "Name": "名古屋花子",  
    "Address": "名古屋市中村区",  
    "Tel": "052-yyy-yyyy",  
]  
// 第一引数に保存データ、第二引数にファイルのパス  
let success = user.writeToFile(_path, atomically: true)  
  
if success {  
    println("保存に成功")  
}  
// NSDictionaryを保存したのでNSDictionaryクラスを使って読み込む。  
var user = NSDictionary(contentsOfFile: _path)  
  
for (key, value) in user {  
    println("\(key)\(value)")  
}  

ポイントとして、NSDictionaryで宣言すること。
プロパティリストで保存する時に使用するwriteToFile関数は、NSArrayやNSDictionaryでないと持っていません。

NSUserDefaultsを使ったデータ保存

例として、Dictionary型の値をNSUserDefaultsへ保存してみます。

// 保存するデータ  
var user = [  
    "Name": "東京一郎",  
    "Address": "東京都",  
    "Tel": "03-xxxx-xxxx",  
]  
// NSUserDefaultsクラスのインスタンスを取得  
let defaults = NSUserDefaults.standardUserDefaults()  
// 保存するオブジェクトをキーと共に設定  
defaults.setObject(user, forKey: "User")  
// ファイルに書き出す  
let success = defaults.synchronize()  
          
if success {  
    println("保存に成功")  
}  
// NSUserDefaultsクラスのインスタンスを取得  
let defaults = NSUserDefaults.standardUserDefaults();  
// キーを使って目的のオブジェクトを取得  
let user = defaults.dictionaryForKey("User")  
          
for (key, value) in user {  
    println("\(key)\(value)")  
}  

まとめ

Objective-Cに慣れ親しんだ方には、見慣れたクラス、関数が登場するので、それほど迷う事なく扱えるのではと思います。
今回のサンプルは、Dictionaryを保存する例で記述しましたが、もちろんArrayも問題なく扱えます。

データの永続化として

  • NSKeyedArchiverを使ったオブジェクトのアーカイブ
  • プロパティリストを使ったデータ保存
  • NSUserDefaultsを使ったデータ保存

を紹介しましたが、SQLiteも扱いたいですよね!

iOSのSQLiteと言えば、ラッパーライブラリのFMDBが有名ではないでしょうか。
次回は、SwiftでFMDBを使ってみたいと思います。