まえがき
Swiftに入門中の友人に向けて説明している記事になります。
SQLite.swift
導入手順
今回はSwift Package Managerで導入する
SQLiteを操作してみる
今回やることは以下
- データベースファイルを作成
- userテーブルを作成
- userの追加(Insert)
- userの検索(Select)
1. データベースファイルを作成
Databaseクラス作成を作成して下記を実装
(ViewControllerクラスで実装してもいいけど分けておいたほうがわかりやすそうなので今回はクラスを分ける)
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側で初期化することを忘れずに。
import UIKit
class ViewController: UIViewController {
var database = Database()
override func viewDidLoad() {
super.viewDidLoad()
}
}
実行後Finderでディレクトリを見てみると無事ファイルが生成されています。
2.userテーブルを作成
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で確認するとちゃんと作成できてることが確認できる。
ごちゃごちゃしてきたのでUserDatastoreってクラス作ってUserテーブルはそっちで触るような実装に整理することにした。
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テーブルの初期データというていで実装する(マイグレーション)
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回実行されてもデータが増えない)
データが入らない場合はsample.dbのファイルごと一回消してやってみる。
4.userの検索(Select)
Userテーブルに入ってるすべてのデータを取得してTableViewで表示してみる
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の関数を実装
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で検索もしたい。
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
}
}
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 初めて使ってみたがわかりやすくて好き。