[iOS8] SwiftData（Swift製SQLiteラッパー）を使ったMVCサンプル

Objective-C時代のSQLiteラッパーといえば、FMDBが定番でした。
SwiftでもObjective-cとの連携が可能なので、FMDBはそのまま利用可能ですが、せっかくなのでSwift製のSQLiteラッパーを探してみました。
##SwiftData
幾つかみつかりましたが、人気がありそうなのが<strong>SwiftData</strong>でした。
<a href="https://github.com/ryanfowler/SwiftData" target="_blank">https://github.com/ryanfowler/SwiftData</a>
直接SQLでかけることはもちろん、エラーハンドリングが簡単だったり、結果がArrayで返してくれたり、記述が少しで済むなど、とても優れているライブラリでしたのでご紹介したいと思います。
##MVCっぽいアプリのサンプル
Xcodeで新しいiOSアプリケーションのプロジェクトを作るときに、いくつかのテンプレートの中にMaster-Detail Applicationというものがあると思います。このテンプレートにSwiftDataライブラリを追加し、Modelを作成してMVC風なアプリを作ってみることにします。

<img class="alignnone size-medium wp-image-102" src="http://tech.eversense.co.jp/wp/wp-content/uploads/2014/10/iOS-Simulator-Screen-Shot-2014.10.08-21.31.43-269x480.png" alt="iOS Simulator Screen Shot 2014.10.08 21.31.43" width="269" height="480" />

サンプルコードはこちら
<a href="https://github.com/eversense/SwiftData-Example" target="_blank">https://github.com/eversense/SwiftData-Example</a>

下記でチュートリアルとして解説していきます。
##準備
###プロジェクト立ち上げ
Xcodeから　File -&gt; New -&gt; Project　をクリック
Master-Detail Applicationを選びます。

<img class="alignnone size-medium wp-image-92" src="http://tech.eversense.co.jp/wp/wp-content/uploads/2014/10/1f4fa8a2667cf34a8a3ab03ad18ae9ea-480x295.png" alt="スクリーンショット 2014-10-08 19.37.38" width="480" height="295" />

適当な名前で保存してください。私はSwiftData-Exampleとしました。
この状態でシュミレーターで起動してみると、tableViewのよくありそうなアプリが動きます。ただ初期状態ではデータを保持するようにはなっていません。そこでSwiftDataを使ってデータを保存するようにしていきます。
###SwiftDataのインストール
本家の説明にはgit submoduleでインストールするように書いてありますが、今回はサンプルなので、githubからZIPでダウンロードしたSwiftData.swiftをそのままプロジェクトにドラックして追加しました。

<img class="alignnone size-full wp-image-94" src="http://tech.eversense.co.jp/wp/wp-content/uploads/2014/10/b88eb0d9adedec030da658436decebdb.png" alt="スクリーンショット 2014-10-08 19.56.37" width="267" height="161" />
###libsqlite3.dylibの追加
次に'libsqlite3.dylib'というライブラリを追加する必要があります。手順は以下のようです。
<ol>
    <li>Xcode 左ペインから、プロジェクトを選択します</li>
    <li>上部のタブから Build Phases を選択</li>
    <li>Link Binary With Libraries を開く</li>
    <li>Link Binary With Libraries の左下の+ をクリック</li>
    <li>Choose framework and libraries add: というダイアログが表示されるので、「sqlite」と入力</li>
    <li>libsqlite3.dylib を選択</li>
    <li>Addをクリックします</li>
</ol>
<img class="alignnone size-medium wp-image-95" src="http://tech.eversense.co.jp/wp/wp-content/uploads/2014/10/9410163bea8335ac102bead0251e0783-480x259.png" alt="スクリーンショット 2014-10-08 19.55.10" width="480" height="259" />

libsqlite3.dylibはobjective-cで書かれているため、Briding-Header.hファイルを追加する必要があります。
<ol>
    <li>左ペインのプロジェクト名のところで右クリック -&gt; New File...を選択
<img class="alignnone size-full wp-image-97" src="http://tech.eversense.co.jp/wp/wp-content/uploads/2014/10/3bf2a99abbb6e7296638ef65bd99242a.png" alt="スクリーンショット 2014-10-08 20.03.49" width="436" height="413" /></li>
    <li>iOSのSourceからHeader Fileを選択し、Next</li>
    <li>「Briding-Header.h」という名前でCreateします</li>
    <li>そのファイルに下記のように<span class="s1">#import </span>"sqlite3.h"を追加します。

```objc:Briding-Header.h
#ifndef SwiftData_Example_Briding_Header_h
#define SwiftData_Example_Briding_Header_h

#import "sqlite3.h"

#endif
```

</li>
    <li>左ペインのプロジェクト名を選択し、Build Settingsのタブを開きます。</li>
    <li>BasicとAllの表示切替をAllにします</li>
    <li>検索窓に「Objective-C Bridging Header」と入力します。
<img class="alignnone size-medium wp-image-96" src="http://tech.eversense.co.jp/wp/wp-content/uploads/2014/10/edad287a65f149789c2f6981a19a7283-480x194.png" alt="スクリーンショット 2014-10-08 20.09.40" width="480" height="194" /></li>
    <li>Objective-C Bridging Headerの項目に「Briding-Header.h」と入力します。
<img class="alignnone size-full wp-image-98" src="http://tech.eversense.co.jp/wp/wp-content/uploads/2014/10/5331a23cb1d20a1adfeb5f5c6c4daff7.png" alt="スクリーンショット 2014-10-08 20.12.01" width="441" height="94" /></li>
</ol>
これでライブラリを使う準備ができました。
##Modelの作成
データベースを操作するModelを作ります。SampleModel.swiftというswiftファイルを追加しました。
###データベースの作成
データベースはデフォルトでSwiftData.sqliteというファイルが自動的に作られます。そのため何もする必要がありません。簡単ですね。
###テーブルの作成
Modelの初期化のタイミングでテーブルを用意していきます。

```swift
import Foundation

class SampleModel {
    
    init() {
        let (tb, err) = SD.existingTables()
        if !contains(tb, "samples") {
            if let err = SD.createTable("samples", withColumnNamesAndTypes: ["data": .StringVal]) {
                //there was an error during this function, handle it here
            } else {
                //no error, the table was created successfully
            }
        }
        println(SD.databasePath())
    }
}

```

テーブルの存在確認をし、なければsamplesというテーブルを作成していきます。カラムとしてString型のdataカラムを追加します。
テーブルを作成すると自動的にIDカラムも追加されるので、ID, dataという２つのカラムのsamplesテーブルが作成されることになります。IDにはAUTOINCREMENTが設定され、レコードが追加されるたびに連番が入ります。
またインラインでエラーハンドリング出来る記述が可能です。SwiftDataの公式に書いてある記述を参考にしています。
また最後の行にprintln(SD.databasePath())と書いて、ログにデータベースのパスを表示させるようにしています。
##SampleModelの呼び出し
メインのコントローラーである、MasterViewControllerでSampleModelを利用します。

```swift
import UIKit

class MasterViewController: UITableViewController {

    var detailViewController: DetailViewController? = nil
    var objects = NSMutableArray()
    let sampleModel = SampleModel() //SampleModelのオブジェクト作成
```

最初のコードの抜粋ですが、一番下の行（7行目）でSampleModelオブジェクトを作成しています。この状態で一度ビルドしてシュミレーターを起動してみると、ログにsqliteのpathが出力されます。Modelの初期化でデータベースのsqliteファイルが作成されて、テーブルが作られます。
##データの追加
###Model側：addメソッド
アプリのプラスボタンを押した時に日時が追加されますが、その情報を保存していくためにまずはモデル側のSampleModelに下記のようなaddメソッドを追加します。

```swift
    func add(data:NSDate) -&gt; Int{
        var result: Int? = nil
        if let err = SD.executeChange("INSERT INTO samples (data) VALUES (?)", withArgs: [data]) {
            //there was an error during the insert, handle it here
        } else {
            //no error, the row was inserted successfully
            let (id, err) = SD.lastInsertedRowID()
            if err != nil {
                //err
            }else{
                //ok
                result = Int(id)
            }
        }
        return result!
    }
```

NSDate型で受け取ったデータを追加します。型を意識せずに保存できるのがいいですね。追加後はInsertしたidを返すようにしています。ちなみにこのコードでエラーが発生したらクラッシュしますが、サンプルなので省略しますね。
###Controller側：sampleModel.add()を使って保存
プラスボタンを押した時にテーブルセルにデータが追加されます。元のソースには下記のように書いてあります。

```swift
    func insertNewObject(sender: AnyObject) {
        objects.insertObject(NSDate.date(), atIndex: 0)
        let indexPath = NSIndexPath(forRow: 0, inSection: 0)
        self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
    }
```

これを次のように改変して、追加すると同時にデータベースに保存出来るようにしていきます。

```swift
    func insertNewObject(sender: AnyObject) {
        let now = NSDate.date()
        let id = sampleModel.add(now)
        let dic = ["ID":id, "data":now]
        objects.insertObject(dic, atIndex: 0)
        let indexPath = NSIndexPath(forRow: 0, inSection: 0)
        self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
    }
```

元ソースではobjectsにNSDate型のオブジェクトをそのまま入れていましたが、あとでデータを削除するときにIDが必要になるので、Dictionary型でobjectsに入れておきます。このままシュミレーターを起動してもクラッシュします。Dictionary型を入れたので、取り出すほうも変更しなくてはいけません。
##データの表示
objectsからデータを取り出す場合には

```swift
let object = objects[indexPath.row] as NSDate
```

のように記述されているところが「prepareForSegueメソッド」と、「tableViewのUITableViewCellを返すメソッド」の２ヶ所にあります。 objectsにはDictionary型を使うように変更したので下記のように書き換えます。

```swift
let object = objects[indexPath.row]["data"] as NSDate

```

この状態でシュミレーターを起動すると正しく動作します。 次に起動時にDBからデータを読み出す処理を作っていきましょう。
##データの取得
###Model側：Selectでデータを読み出す
今度はDBに保存したデータを起動時に読み込むようにしていきます。まずModel側の準備です。下記のようなgetAllメソッドを書きました。

```swift
    func getAll() -&gt; NSMutableArray {
        var result = NSMutableArray()
                let (resultSet, err) = SD.executeQuery("SELECT * FROM samples ORDER BY ID DESC")
        let dateFormatter = NSDateFormatter()
        if err != nil {
            
        } else {
            for row in resultSet {
                if let id = row["ID"]?.asInt() {
                    let dataStr = row["data"]?.asString()!
                    dateFormatter.dateFormat = "YYYY/MM/dd HH:mm:ss"
                    let data = dateFormatter.dateFromString(dataStr!)
                    let dic = ["ID":id, "data":data!]
                    result.addObject(dic)
                }
            }
        }
        return result
    }
```

samplesテーブルの全件をID降順で取得します。その後、for inで回し、NSMutableArrayに["ID":id, "data":data]というDictionaryを入れて返しています。
###Controller側：起動時、objectsにデータを追加
起動時に走るviewDidLoadに下記の一文加えるだけで、OKです。

```swift
objects = sampleModel.getAll()
```

MVCらしく簡潔な記述になりますね。 シュミレーターを起動してみると、これまで追加してきたデータが表示されるようになると思います。ここまででデータの追加、取得まで出来るようになりました。
## データの削除
このアプリではデータの削除機能もあるので、最後にそれも実装していきます。
###Model側：deleteメソッド
idを元に、レコードを削除出来るように下記を記述します。

```swift
    func delete(id:Int) -&gt; Bool {
        if let err = SD.executeChange("DELETE FROM samples WHERE ID = ?", withArgs: [id]) {
            //there was an error during the insert, handle it here
            return false
        } else {
            //no error, the row was inserted successfully
            return true
        }
    }
```

Controller側：deleteを追加する

MasterViewControllerの一番最後のメソッドに上記で用意したdeleteを使ってテーブルから削除をおこないます。

```swift
    override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
        if editingStyle == .Delete {
            sampleModel.delete(objects[indexPath.row]["ID"] as Int)
            objects.removeObjectAtIndex(indexPath.row)
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        } else if editingStyle == .Insert {
            // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view.
        }
    }
```

4行目が追加された行です。 これで全ての機能がデータベースと連携することになりました。
以上、お疲れ様でした！
##最後に
MVCのようにモデルを作ることで、このような機能追加でもコントローラーへの記述がわずかだったのがわかると思います。iOSでデータベースを利用する場合にはラッパーを使って簡単にDBを扱い、モデルを作成してコントローラーを簡潔にしたいですね！


こちらにサンプルソースコードを置いておきます。
<a href="https://github.com/eversense/SwiftData-Example" target="_blank">https://github.com/eversense/SwiftData-Example</a>