66
67

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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

66
67
6

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
66
67

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?