iOS
Swift

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

More than 3 years have passed since last update.

Objective-C時代のSQLiteラッパーといえば、FMDBが定番でした。

SwiftでもObjective-cとの連携が可能なので、FMDBはそのまま利用可能ですが、せっかくなのでSwift製のSQLiteラッパーを探してみました。


SwiftData

幾つかみつかりましたが、人気がありそうなのがSwiftDataでした。

https://github.com/ryanfowler/SwiftData

直接SQLでかけることはもちろん、エラーハンドリングが簡単だったり、結果がArrayで返してくれたり、記述が少しで済むなど、とても優れているライブラリでしたのでご紹介したいと思います。


MVCっぽいアプリのサンプル

Xcodeで新しいiOSアプリケーションのプロジェクトを作るときに、いくつかのテンプレートの中にMaster-Detail Applicationというものがあると思います。このテンプレートにSwiftDataライブラリを追加し、Modelを作成してMVC風なアプリを作ってみることにします。

iOS Simulator Screen Shot 2014.10.08 21.31.43

サンプルコードはこちら

https://github.com/eversense/SwiftData-Example

下記でチュートリアルとして解説していきます。


準備


プロジェクト立ち上げ

Xcodeから File -> New -> Project をクリック

Master-Detail Applicationを選びます。

スクリーンショット 2014-10-08 19.37.38

適当な名前で保存してください。私はSwiftData-Exampleとしました。

この状態でシュミレーターで起動してみると、tableViewのよくありそうなアプリが動きます。ただ初期状態ではデータを保持するようにはなっていません。そこでSwiftDataを使ってデータを保存するようにしていきます。


SwiftDataのインストール

本家の説明にはgit submoduleでインストールするように書いてありますが、今回はサンプルなので、githubからZIPでダウンロードしたSwiftData.swiftをそのままプロジェクトにドラックして追加しました。

スクリーンショット 2014-10-08 19.56.37


libsqlite3.dylibの追加

次に'libsqlite3.dylib'というライブラリを追加する必要があります。手順は以下のようです。




  1. Xcode 左ペインから、プロジェクトを選択します



  2. 上部のタブから Build Phases を選択



  3. Link Binary With Libraries を開く



  4. Link Binary With Libraries の左下の+ をクリック



  5. Choose framework and libraries add: というダイアログが表示されるので、「sqlite」と入力



  6. libsqlite3.dylib を選択



  7. Addをクリックします





スクリーンショット 2014-10-08 19.55.10

libsqlite3.dylibはobjective-cで書かれているため、Briding-Header.hファイルを追加する必要があります。




  1. 左ペインのプロジェクト名のところで右クリック -> New File...を選択

    スクリーンショット 2014-10-08 20.03.49



  2. iOSのSourceからHeader Fileを選択し、Next



  3. 「Briding-Header.h」という名前でCreateします



  4. そのファイルに下記のように#import "sqlite3.h"を追加します。


    Briding-Header.h

    #ifndef SwiftData_Example_Briding_Header_h
    
    #define SwiftData_Example_Briding_Header_h

    #import "sqlite3.h"

    #endif





  5. 左ペインのプロジェクト名を選択し、Build Settingsのタブを開きます。



  6. BasicとAllの表示切替をAllにします



  7. 検索窓に「Objective-C Bridging Header」と入力します。

    スクリーンショット 2014-10-08 20.09.40



  8. Objective-C Bridging Headerの項目に「Briding-Header.h」と入力します。

    スクリーンショット 2014-10-08 20.12.01





これでライブラリを使う準備ができました。


Modelの作成

データベースを操作するModelを作ります。SampleModel.swiftというswiftファイルを追加しました。


データベースの作成

データベースはデフォルトでSwiftData.sqliteというファイルが自動的に作られます。そのため何もする必要がありません。簡単ですね。


テーブルの作成

Modelの初期化のタイミングでテーブルを用意していきます。

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という2つのカラムのsamplesテーブルが作成されることになります。IDにはAUTOINCREMENTが設定され、レコードが追加されるたびに連番が入ります。

またインラインでエラーハンドリング出来る記述が可能です。SwiftDataの公式に書いてある記述を参考にしています。

また最後の行にprintln(SD.databasePath())と書いて、ログにデータベースのパスを表示させるようにしています。


SampleModelの呼び出し

メインのコントローラーである、MasterViewControllerでSampleModelを利用します。

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メソッドを追加します。

    func add(data:NSDate) -> 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()を使って保存

プラスボタンを押した時にテーブルセルにデータが追加されます。元のソースには下記のように書いてあります。

    func insertNewObject(sender: AnyObject) {

objects.insertObject(NSDate.date(), atIndex: 0)
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}

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

    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からデータを取り出す場合には

let object = objects[indexPath.row] as NSDate

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

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

この状態でシュミレーターを起動すると正しく動作します。 次に起動時にDBからデータを読み出す処理を作っていきましょう。


データの取得


Model側:Selectでデータを読み出す

今度はDBに保存したデータを起動時に読み込むようにしていきます。まずModel側の準備です。下記のようなgetAllメソッドを書きました。

    func getAll() -> 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です。

objects = sampleModel.getAll()

MVCらしく簡潔な記述になりますね。 シュミレーターを起動してみると、これまで追加してきたデータが表示されるようになると思います。ここまででデータの追加、取得まで出来るようになりました。


 データの削除

このアプリではデータの削除機能もあるので、最後にそれも実装していきます。


Model側:deleteメソッド

idを元に、レコードを削除出来るように下記を記述します。

    func delete(id:Int) -> 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を使ってテーブルから削除をおこないます。

    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を扱い、モデルを作成してコントローラーを簡潔にしたいですね!

こちらにサンプルソースコードを置いておきます。

https://github.com/eversense/SwiftData-Example