エンジニア

Swiftでのデータ永続化

投稿日:2014年7月29日 更新日:

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

本日は、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を使ってみたいと思います。

採用情報

ワンダープラネットでは、一緒に働く仲間を幅広い職種で募集しております。

-エンジニア
-

© WonderPlanet Inc.