Help us understand the problem. What is going on with this article?

iOSにデータベースを組み込んでみる。最新GRDBの使い方を解説

はじめに

経緯

圧倒的要員不足で、私がiOSアプリの制作を学ばなければならなかったのだが、なにから学べばいいのかわからなかった。いろいろ考えた結果、メモアプリを制作することになった。

GRDBとは

iOSとかmacOS向けのデータベースを制作できるようにするものらしい。早くて安全とかなんとか...一番これがしっくりきたので選んだ。Androidのようにもっと簡単にできないものか...

1.設定

まず公式のGitHubからzipをダウンロード。)MacOSなら自動で解凍してくれるらしい。プロジェクトファイル(ProjectName.xcodeproj)を選択した状態で、ツールバーの「File」>「Add File to "ProjectName"」を選択し、解凍したファイル内のGRDB.xcodeprojを選択。

スクリーンショット 2019-07-25 1.11.27.png

スクリーンショット 2019-07-25 1.12.38.png

ProjectName.xcodeproj 直下にGRDB.xcodeprojがインポートされると思います。ProjectName.xcodeprojを選択した状態に戻し、Build Phasesタブを選択しましょう。Target Dependenciesという項目を開き、+を押してGRDBiOSを追加して下さい。

スクリーンショット 2019-07-25 1.13.42.png

次はGeneralタブへ移り、スクロールしてEmbedded BinariesGRDB.framwork(iOS)(なお、カッコ内は薄文字)を追加。これでOKです。
スクリーンショット 2019-07-25 1.14.27.png
スクリーンショット 2019-07-25 1.17.25.png

2.データベースを作る

新しいSwift Fileをプロジェクト内に作ってこう書きましょう。

UserTable.swift
import Foundation
import GRDB

class UserTable: Record {
    // テーブル名
    override static var databaseTableName: String {
        return "memoList"
    }

    var id: Int64?
    var title: String
    var body: String

    static func create(_ db: Database) throws {
        try db.create(table: databaseTableName, body: { (t: TableDefinition) in
            t.column("id", .integer).primaryKey(onConflict: .replace, autoincrement: true)
            t.column("title", .text).notNull().unique() // 重複を許さないのなら.unique()をつける
            t.column("body", .text).notNull() // Nullを許さないのなら.notNull()をつける
        })
    }

    enum Columns {
        static let id = Column("id")
        static let title = Column("title")
        static let body = Column("body")
    }

    init(title: String, body: String) {
        self.title = title
        self.body = body
        super.init()
    }

    required init(row: Row) {
        self.id = row["id"]
        self.title = row["title"]
        self.body = row["body"]
        super.init(row: row)
    }

    override func encode(to container: inout PersistenceContainer) {
        container["id"] = self.id
        container["title"] = self.title
        container["body"] = self.body
    }

    override func didInsert(with rowID: Int64, for column: String?) {
        self.id = rowID
    }
}

これでデータベースがどんな形なのかを指定できた。もちろん追記しても構わないです。しかし、これではまだ作られてないのでさらに別のファイルを追加しましょう。

DatabaseHelper.swift
import Foundation
import GRDB

class DatabaseHelper {

    private struct Const {
        static let dbFileName = NSTemporaryDirectory() + "database.db"
    }

    init() {
        self.creatDatabase()
    }

    func inDatabase(_ block: (Database) throws -> Void) -> Bool {
        do {
            let dbQueue = try DatabaseQueue(path: Const.dbFileName)
            try dbQueue.inDatabase(block)
        }catch _ {
            return false
        }
        return true
    }

    private func creatDatabase() {
        if FileManager.default.fileExists(atPath: Const.dbFileName) {
            return
        }
        let result = inDatabase {(db) in
            try UserTable.create(db)
        }

        if !result {
            do {try FileManager.default.removeItem(atPath: Const.dbFileName)} catch {}
        }
    }
}

特に変えるところはないのでコピペで大丈夫だと思います。アプリが初めて起動した時に自動で呼ばれ、データベースファイルが生成されます。

3.使い方

3.1 行の追加

let helper = DatabaseHelper()
let result = helper.inDatabase{(db) in
    let user = UserTable(title: /*titleの文字列*/ ?? "", body: /*bodyの文字列*/ ?? "")
    try user.insert(db)
}
if (!result) {
    // 失敗
} else {
    // 成功
}

3.2 行の選択(削除や上書きの時に使う)

titleの文字列をもとに探す場合。

let helper = DatabaseHelper()
let result = helper.inDatabase{(db) in
    let user = try UserTable.filter(sql: "title = ?", arguments: [/*titleの文字列*/]).fetchOne(db)
}

これはSQL文を使った特定方法。.filter()で条件に該当する行を全てマークして.fetchOne(db)で一つだけ取り出す。
titleはデータベースを設定する際、.unique()を設定してるので重複はない。だから一つだけでいいのです。また、sql'で条件をColumn名 = ?と書き、その値は後ろのarguments:で配列で渡す。ちなみに複数条件はこう。(多分orもできる)

let user = try UserTable.filter(sql: "title = ? AND body = ?", arguments: [/*titleの文字列*/, /*bodyの文字列*/]).fetchOne(db)

3.3 行の変更

let helper = DatabaseHelper()
let result = helper.inDatabase{(db) in
    let user = try UserTable.filter(sql: "title = ?", arguments: [/*変更前のtitleの文字列*/]).fetchOne(db) // さっきの
    user?.title = /*変更後のtitleの文字列*/!
    user?.body = /*変更後のbodyの文字列*/
    try user?.update(db)
}

3.4 行の削除

let helper = DatabaseHelper()
    let databaseResult = helper.inDatabase{(db) in
    let user = try UserTable.filter(sql: "title = ?", arguments: [/*変更前のtitleの文字列*/]).fetchOne(db)
    try user?.delete(db)
}

4.まとめ

今回はiOSやMacでデータベースを利用してみました。詳しいやり方と参考にしたサイトは以下です。ぜひ見てみて下さい。
GRDB.swiftというSQLiteライブラリがイイ感じだった - Qiita
[macOS][iOS] Sqliteを使ってみる : プログラミング・メモ
Twitter: https://twitter.com/Cyber_Hacnosuke (フォローしてくださいお願いします。)
いいねもお願いします。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away