1. 项目中添加 libsqlite3.tbd 库,点击项目,选择 TARGETS 中项目,在Frameworks,libraries,and Embedded Content 选项中 点击 +,索搜 libsqlite3,选择并添加 libsqlite3.tbd
2. libsqlite3 使用的是OC编写, 需要创建侨接文件 SQLite-Bridge.h,项目配置桥接文件 项目名称/文件名(SQLite-Bridge.h), 引用头文件
//
// SQLite-Bridge.h
//
#import <sqlite3.h>
3. 创建 SQLite 管理器 SQLiteManager.swift
//
// SQLiteManager.swift
//
import Foundation
///SQLite 管理器
/**
- SQLite 框架是纯 C 语言的框架
- 所有函数都是以 sqlite3_ 开始的
*/
class SQLiteManager {
/// 单例
static let sharedManager = SQLiteManager()
//全局数据库操作句柄
var db: OpaquePointer? = nil
/// 打开数据库
/// - Parameter dbName: 数据库名
func openDB(dbName: String){
var path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).last!
path = (path as NSString).appendingPathComponent(dbName)
print(path)
/**
参数
1.数据库的全路径 CChar C语言的字符串
2.全局数据库访问 ‘句柄’ ->指针,后续所有对数据库的操作,全部基于此句柄
返回值 如果 == SQLITE_OK 表示成功
如果数据库不存在,会创建数据库,然后在打开数据库
如果数据存在,会直接打开数据库
*/
if sqlite3_open(path, &db) != SQLITE_OK {
print("打开数据库失败")
return
}
if createTable1(){
print("创建数据表成功")
}else{
print("创建数据表失败")
}
}
//MARK: - 数据查询操作
/// 执行 SQL 返回数据结果集合
/// - Parameter sql: sql
func execRecordSet(sql: String) -> [[String: Any]]?{
//1.预编译 SQL
/**
参数
1. 全局数据库句柄
2. 要执行 SQL 的 C 语言的字符串
3. 要执行 SQL 的以字节为单位的长度,但是,如果传入 -1, SQLite 框架会自动计算
4. STMT - 预编译的指令句柄
-- 后续针对‘本次查询’所有操作,全部基于此句柄
-- 必须注意的,句柄一定要释放
-- 编译完成后,可以理解为一个临时的数据集合,通过 step 函数,能够顺序获取其中的结果
5. 关于 STMT 尾部参数的指针,通常传入 nil
返回值
如果编译成功,表示 SQL 能够正常执行,返回 SQLITE_OK
*/
var stmt: OpaquePointer? = nil
if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) != SQLITE_OK {
print("SQL 错误")
//释放
sqlite3_finalize(stmt)
return nil
}
//创建字典的数组
var result = [[String: Any]]()
//单步执行获取结果 - ROW 对应一条完整的记录
while sqlite3_step(stmt) == SQLITE_ROW {
//将字典记录添加到数组
result.append(record(stmt: stmt!))
}
//print(result)
//释放 stmt
sqlite3_finalize(stmt)
//返回结果
return result
}
///从 stmt 中获取当前的记录内容
/**
提示: 在获取一条记录的完整信息时,所有的函数开头都是 sqlite3_column_
*/
private func record(stmt: OpaquePointer) -> [String: Any]{
//1.知道记录的列数
let cols = sqlite3_column_count(stmt)
//2.创建单条记录的字典
var row = [String: Any]()
//3.循环每一列,获得每一列的对应内容
for col in 0..<cols{
//1>列名
let cName = sqlite3_column_name(stmt, col)
let name = String(cString: cName!, encoding: .utf8)
//2>数据类型
let type = sqlite3_column_type(stmt, col)
var value: Any?
switch type {
case SQLITE_FLOAT: //小数
value = sqlite3_column_double(stmt, col)
case SQLITE_INTEGER: // 整数
value = Int(sqlite3_column_int64(stmt, col))
case SQLITE_TEXT:
//记录 C 语言的字符这次啊
value = String(cString: sqlite3_column_text(stmt, col)!)
case SQLITE_NULL: //空值,一般数据库中允许字段为 nil,但是 OC 的字典不能插入 nil
value = NSNull() //NSNull 就是专门向字典和数组中插入控制使用的
default:
print("不支持的数据类型 ")
}
//print("\(name) --- \(type) --- \(value)")
//取到一项内容,设置字典
row[name!] = value
}
//print(row)
return row
}
//MARK: - 数据操作函数
/// 执行 SQL 更新 / 删除 数据
/// - Parameter sql: sql
/// - Returns: 返回修改的数据行数
func execUpdate(sql: String) -> Int{
//1.执行 SQL - 如果 SQL 错误会返回 -1
if !execSQL(sql: sql){
//执行失败返回 -1
return -1
}
//2.执行成功,返回影响的数据行数
return Int(sqlite3_changes(db))
}
/// 执行 SQL 插入数据
/// - Parameter sql: sql
/// - Returns: 返回自动增长的 id
func execInsert(sql: String) -> Int{
//1.执行 SQL
if !execSQL(sql: sql){
//执行失败返回 -1
return -1
}
//2.执行成功,返回自动增长的 id
return Int(sqlite3_last_insert_rowid(db))
}
/// 执行 SQL 指令
/// - Parameter sql: sql
/// - Returns: 返回是否正确
func execSQL(sql: String) -> Bool{
/**
参数
1.数据库全局句柄
2.要执行的 SQL
3.callback,执行完成 SQL 之后,调用的 C 语言函数指针,通常传入 nil
4.第三个参数 callback, 函数参数的地址,通常传入 nil
5.错误信息,有其他方式获取执行情况,通常传入 nil
返回值 如果 == SQLITE_OK 表示成功
*/
return sqlite3_exec(db, sql, nil, nil, nil) == SQLITE_OK
}
//MARK: -创建数据表
//开发过程中,SQL 语句容易出问题
//可以吧 sql 输出,借助 navicat 铺助做语法检查
//2.拼接字符串的时候,末尾添加 \n 可以避免拼接字符串的时候,造成字符串连接错误
//最好添加 \n
private func createTable() -> Bool{
//1.准备 SQL
let sql = "CREATE TABLE IF NOT EXISTS 'T_Person' ( \n" +
"'id' INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, \n" +
"'name' TEXT, \n" +
"'age' INTEGER, \n" +
"'height' NUMBER, \n" +
"'title' TEXT \n" +
");"
//print(sql)
return execSQL(sql: sql)
}
//MARK: -创建数据表
///创建数据表 - 商业应用程序 主流的方法
/// - Returns: 是否成功
private func createTable1() -> Bool{
//1.从 bundle 中加载 sql 文件
let path = Bundle.main.path(forResource: "db.sql", ofType: nil)!
//2.读取 SQL 的字符串
let sql = try! String(contentsOfFile: path)
//3.执行 sql
//print(sql)
return execSQL(sql: sql)
}
}
4. 创建db.sql 文件
-- 创建个人表 --
CREATE TABLE IF NOT EXISTS "T_Person" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"name" TEXT,
"age" INTEGER,
"height" NUMBER
);
-- 创建图书表 --
CREATE TABLE IF NOT EXISTS "T_Book" (
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
"bookName" TEXT
);
5. 创建模型文件 Person.swift
//
// Person.swift
//
import UIKit
class Person: NSObject {
@objc var id = 0
@objc var name: String?
@objc var age = 0
@objc var height:Double = 0
init(dict: [String: Any]){
super.init()
setValuesForKeys(dict)
}
override func setValue(_ value: Any?, forUndefinedKey key: String) {
}
override var description: String{
let keys = ["id", "name", "age", "height"]
return dictionaryWithValues(forKeys: keys).description
}
//MARK: - 数据库查询
class func persons() -> [Person]? {
//1.准备 SQL
let sql = "SELECT id, name, age, height FROM T_Person;"
//2.执行 SQL -> 字典到数组
guard let array = SQLiteManager.sharedManager.execRecordSet(sql: sql) else{
return nil
}
//3. 遍历字典,字典转模型
var arrayM = [Person]()
for dict in array {
arrayM.append(Person(dict: dict))
}
return arrayM
}
//MARK: - 数据库操作
///将当前对象 id 对应的数据删除
func deletePerson() -> Bool{
//1.准备 SQL
let sql = "DElETE FROM T_Person WHERE id = \(id);"
//2.执行 SQL - 记录影响的行数
let row = SQLiteManager.sharedManager.execUpdate(sql: sql)
print("影响数据的行数 \(row)")
return row>0
}
///将当前对象 id 对应的数据进行修改
func updatePerson() -> Bool{
//1.准备 SQL
let sql = "UPDATE T_Person SET name = '\(name!)', age = \(age), height = \(height) \n" +
"WHERE id = \(id);"
//2.执行 SQL - 记录影响的行数
let row = SQLiteManager.sharedManager.execUpdate(sql: sql)
return row > 0
}
///将当前对象添加到数据库
func insertPerson() -> Bool{
//1.准备 SQL
let sql = "INSERT INTO T_Person (name, age, height) VALUES ('\(name ?? "")', \(age), \(height));"
//2. 执行SQL
id = SQLiteManager.sharedManager.execInsert(sql: sql)
return id > 0
}
}
6. 测试方法
6.1 打开数据库并创建表,在 SceneDelegate.swift 方法中执行
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
guard let _ = (scene as? UIWindowScene) else { return }
//持久化连接[网络长连接 - 即时通讯]
//只做打开数据库的动作,不做关闭数据库的动作!
//后续再使用时,直接做读写操作!效率更高!移动端开发数据库,通常都是持久化连接!
SQLiteManager.sharedManager.openDB(dbName: "demo.db")
}
6.2 SQLite 基本操作
//
// ViewController.swift
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//demoInsert()
//demoUpdate()
//demoDelete()
//print((Person.persons() ?? []) as NSArray)
//manyPersons2()
manyPersons4()
}
/// 开发中出现失败
func manyPersons4(){
//取绝对时间的函数
//CFAbsoluteTimeGetCurrent() - 会收到 ‘系统服务’ 的影响,在做性能测试的时候,可能会有误差!
//CACurrentMediaTime() - 只和硬件时间有关,做性能测试更准确
//1.开始时间
let start = CACurrentMediaTime()
//开启事务
SQLiteManager.sharedManager.execSQL(sql: "BEGIN TRANSACTION;")
for i in 0..<10000{
let p = Person(dict: ["name": "张三-\(i)", "age": 22, "height": 1.8 ])
/// 插入失败
if !p.insertPerson(){
//回滚事务
SQLiteManager.sharedManager.execSQL(sql: "ROLLBACK TRANSACTION;")
break
}
}
//提交事物
SQLiteManager.sharedManager.execSQL(sql: "COMMIT TRANSACTION;")
//2.结束时间
print("完成 \(CACurrentMediaTime() - start)")
}
/// 模拟失败
func manyPersons3(){
//1.开始时间
let start = CACurrentMediaTime()
//开启事务
SQLiteManager.sharedManager.execSQL(sql: "BEGIN TRANSACTION;")
for i in 0..<10000{
Person(dict: ["name": "张三-\(i)", "age": 22, "height": 1.8 ]).insertPerson()
if i==1000 { //
//回滚事务
SQLiteManager.sharedManager.execSQL(sql: "ROLLBACK TRANSACTION;")
break
}
}
//提交事物
SQLiteManager.sharedManager.execSQL(sql: "COMMIT TRANSACTION;")
//2.结束时间
print("完成 \(CACurrentMediaTime() - start)")
}
/**
事务
1.在 SQLite 数据库操作中,如果 “不显式”开启事务,每一条数据库的操作指令都会开启事务,执行完毕后,提交事务
2.如果 显式 的开启事务, SQLite 不再开启事务
*/
//插入大量数据 - 使用事务只需要 0.7s
func manyPersons2(){
//1.开始时间
let start = CACurrentMediaTime()
//开启事务
SQLiteManager.sharedManager.execSQL(sql: "BEGIN TRANSACTION;")
for i in 0..<10000{
Person(dict: ["name": "张三-\(i)", "age": 22, "height": 1.8 ]).insertPerson()
}
//提交事物
SQLiteManager.sharedManager.execSQL(sql: "COMMIT TRANSACTION;")
//2.结束时间
print("完成 \(CACurrentMediaTime() - start)")
}
//插入大量数据 7秒
func manyPersons1(){
//1.开始时间
let start = CACurrentMediaTime()
for i in 0..<10000{
Person(dict: ["name": "张三-\(i)", "age": 22, "height": 1.8 ]).insertPerson()
}
//2.结束时间
print("完成 \(CACurrentMediaTime() - start)")
}
//删除操作
func demoDelete(){
let p = Person(dict: ["id": 2])
if p.deletePerson(){
print("删除成功 \(p)")
}else{
print("删除失败")
}
}
//修改操作
func demoUpdate(){
let p = Person(dict: ["id": 1, "name": "笑笑", "age": 17, "height": 1.68 ])
if p.updatePerson(){
print("修改成功 \(p)")
}else{
print("修改失败")
}
}
//插入操作
func demoInsert(){
let p = Person(dict: ["name": "张三", "age": 22, "height": 1.8 ])
if p.insertPerson(){
print("插入成功 \(p)")
}else{
print("插入失败")
}
}
}