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

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

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

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

![スクリーンショット 2019-07-25 1.11.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263233/ee2ded91-fbc6-3f39-74ca-613710180ce2.png)

![スクリーンショット 2019-07-25 1.12.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263233/9e58e9fc-33df-cfc8-3c09-3332a4ba4c98.png)

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

![スクリーンショット 2019-07-25 1.13.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263233/436324de-dfec-debc-8ef8-6d5498085d99.png)


次は``General``タブへ移り、スクロールして``Embedded Binaries``に``GRDB.framwork(iOS)``(なお、カッコ内は薄文字)を追加。これでOKです。
![スクリーンショット 2019-07-25 1.14.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263233/6fc60ba5-bee1-eef4-053f-359842ba1e31.png)
![スクリーンショット 2019-07-25 1.17.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/263233/e6b61ca7-f506-f076-9624-a037c887ad24.png)

# 2.データベースを作る
新しいSwift Fileをプロジェクト内に作ってこう書きましょう。

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

```swift: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 行の追加

```swift
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``の文字列をもとに探す場合。

```swift
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もできる)

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

## 3.3 行の変更
```swift
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 行の削除
```swift
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](https://qiita.com/owen/items/84fb05931946120523ed)
[[macOS][iOS] Sqliteを使ってみる : プログラミング・メモ](http://m-miya.blog.jp/archives/1064332237.html)
Twitter: https://twitter.com/Cyber_Hacnosuke (フォローしてくださいお願いします。)
いいねもお願いします。