使用系统自带SQLite

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("插入失败")
        } 
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hanyang Li

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值