LoginSignup
3
5

More than 3 years have passed since last update.

SQLite.swift でSQLiteを操作する

Posted at

まえがき

Swiftに入門中の友人に向けて説明している記事になります。

SQLite.swift

導入手順

今回はSwift Package Managerで導入する

  1. Xcodeから File > Swift Package > Add Package Dependency を選択
    スクリーンショット 2021-06-06 16.29.56.png

  2. https://github.com/stephencelis/SQLite.swift.git で検索
    スクリーンショット 2021-06-06 16.32.47.png

  3. Versionを選択(今回は特に何も考えずデフォルト設定)
    スクリーンショット 2021-06-06 16.33.07.png

  4. Finish!
    スクリーンショット 2021-06-06 16.34.15.png

XcodeのUI上で入れたPackageも確認できる!
スクリーンショット 2021-06-06 16.39.21.png

SQLiteを操作してみる

今回やることは以下
- データベースファイルを作成
- userテーブルを作成
- userの追加(Insert)
- userの検索(Select)

1. データベースファイルを作成

Databaseクラス作成を作成して下記を実装
(ViewControllerクラスで実装してもいいけど分けておいたほうがわかりやすそうなので今回はクラスを分ける)

Database.swift
import Foundation
import SQLite

let FILE_NAME = "sample.db"

class Database {
  var db: Connection
  init() {
    // DBファイルの作成先のパスを生成
    let filePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(FILE_NAME).path
    // DBファイル作成/開く
    db = try! Connection(filePath)
  }
}

ちゃんとViewController側で初期化することを忘れずに。

ViewController.swift
import UIKit

class ViewController: UIViewController {
  var database = Database()

  override func viewDidLoad() {
    super.viewDidLoad()
  }
}

実行後Finderでディレクトリを見てみると無事ファイルが生成されています。
スクリーンショット 2021-06-06 17.15.30.png

2.userテーブルを作成

Database.swift
class Datastore {
  var db: Connection
  init() {
    // DBファイルの作成先のパスを生成
    let filePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(FILE_NAME).path
    // DBファイル作成/開く
    db = try! Connection(filePath)

    // DBにテーブル作成
    let users = Table("users")
    do {
      try db.run(users.create { t in
          t.column(Expression<Int64>("id"), primaryKey: true)
          t.column(Expression<String?>("name"))
          t.column(Expression<String>("email"), unique: true)
      })
    } catch {}
  }
}

do {} catch {} でくくってる理由は2回目移行で動かす時にすでにテーブルが作成されている状態になるのでもう一度作ろうとしてエラーになるのでそれを無視するため。(もっといい書き方ありそうなきもするけど一旦これでいいかな)

SQLiteの中を見れるGUIで確認するとちゃんと作成できてることが確認できる。
スクリーンショット 2021-06-06 17.30.32.png

ごちゃごちゃしてきたのでUserDatastoreってクラス作ってUserテーブルはそっちで触るような実装に整理することにした。

Database.swift
class Database {
  let db: Connection
  let userDatastore: UserDatastore

  init() {
    // DBファイルの作成先のパスを生成
    let filePath = try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false).appendingPathComponent(FILE_NAME).path
    // DBファイル作成/開く
    db = try! Connection(filePath)

    // UserDatastoreを初期化
    userDatastore = UserDatastore(db: db)
  }
}
class UserDatastore {
  private let table = Table("users")
  private let id = Expression<Int64>("id")
  private let name = Expression<String>("name")
  private let email = Expression<String>("email")
  private let db: Connection

  init(db: Connection) {
    self.db = db
    do {
      try self.db.run(table.create { t in
        t.column(Expression<Int64>("id"), primaryKey: true)
        t.column(Expression<String>("name"))
        t.column(Expression<String>("email"), unique: true)
      })
    } catch {}
  }  
}

3. userの追加(Insert)

初期データでも入れてみる。
userテーブルの初期データというていで実装する(マイグレーション)

Database.swift
class Database {
  省略...
}

class UserDatastore {
  private let table = Table("users")
  private let id = Expression<Int64>("id")
  private let name = Expression<String>("name")
  private let email = Expression<String>("email")
  private let db: Connection

  init(db: Connection) {
    self.db = db
    do {
      try self.db.run(table.create { t in
        t.column(Expression<Int64>("id"), primaryKey: true)
        t.column(Expression<String>("name"))
        t.column(Expression<String>("email"), unique: true)
      })
      // 初期データを入れる
      let migrationItems = [
        ["name": "Alice", "email":"alice@mac.com"],
        ["name": "Bob", "email":"bob@mac.com"]
      ]
      migrationItems.forEach { row in
        try? insert(name: row["name"]!, email: row["email"]!)
      }
    } catch {}
  }

  func insert(name: String, email: String) throws {
    let insert = table.insert(self.name <- name, self.email <- email)
    try db.run(insert)
  }
}

内容的にはinsertの関数を作成、データでぐるぐるループしてinsertの関数を実行する。

try self.db.run(table.create... の実行で2回目移行はエラーが発生して後続の処理(ループしてインサートする処理)が動かなくなるので初回の1回目しか実行されないため初期データが2重で入ることもない。
(ちゃんとマイグレーションするなら作り込んだほうがいいかもだけど一旦これで。)

中みるとちゃんとデータが入ってる。(2回実行されてもデータが増えない)
スクリーンショット 2021-06-06 18.12.20.png

データが入らない場合はsample.dbのファイルごと一回消してやってみる。

4.userの検索(Select)

Userテーブルに入ってるすべてのデータを取得してTableViewで表示してみる

Database.swift
class UserDatastore {
  省略...    
  func find() -> [User] {
    var results = [User]()
    // エラー起こした場合は空の配列を返却
    guard let users = try? db.prepare(table) else {
      return results
    }
    for row in users {
      results.append(User(id: row[self.id], name: row[self.name], email: row[self.email]))
    }
    return results
  }  
}

class User {
  let id: Int64
  let name: String
  let email: String
  init(id: Int64, name: String, email: String) {
    self.id = id
    self.name = name
    self.email = email
  }
}

Userクラスを追加したのとUserDatastoreクラスにfindの関数を実装

ViewController.swift
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

  var database = Database()
  var datas = [User]()

  override func viewDidLoad() {
    super.viewDidLoad()
    datas = database.userDatastore.find()
  }

  // Sectionの個数
  func numberOfSections(in tableView: UITableView) -> Int {
    return 1
  }

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return datas.count
  }

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let identifier = "HogeCell"
    let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath)
    cell.textLabel?.text = datas[indexPath.row].name
    return cell
  }
}

ユーザIDで検索もしたい。

Database.swift
class UserDatastore {
  省略...    
  func findById(id: Int64) -> [User] {
    var results = [User]()
    // エラー起こした場合は空の配列を返却
    guard let users = try? db.prepare(table.where(self.id == id)) else {
      return results
    }
    for row in users {
      results.append(User(id: row[self.id], name: row[self.name], email: row[self.email]))
    }
    return results
  }
}
ViewController.swift
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

  var database = Database()
  var datas = [User]()

  override func viewDidLoad() {
    super.viewDidLoad()
//    datas = database.userDatastore.find()
    datas = database.userDatastore.findById(id: 2)
  }
  省略...

感想

初めてちゃんとQita書いた気がするけどなんかちょっとモチベーション上がったから定期的にかけるといいな。
Swift Package Manager 初めて使ってみたがわかりやすくて好き。

3
5
0

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
3
5