Swift3.0 SQLite使用、性能優(yōu)化巾钉、線程安全

效果圖附上:
<br />
插入數(shù)據(jù)


insert.gif

<br />
刪除數(shù)據(jù)


delete.gif

<br />
更新數(shù)據(jù)


update.gif

<br />
查找數(shù)據(jù)


select.gif

<br />


排序數(shù)據(jù)

<br />
分頁(yè)檢索數(shù)據(jù)


limit.gif

<br />
Other數(shù)據(jù)- 這里時(shí)間限制翘狱,不演示全部


other.gif
  • 前言

上一篇簡(jiǎn)單的介紹了SQLite的基本使用之后,現(xiàn)在我們開(kāi)始在代碼中去完成對(duì)數(shù)據(jù)庫(kù)的操作砰苍。
內(nèi)容點(diǎn):
PS:使用C函數(shù)完成盒蟆,不使用第三方框架

  • 集成SQLite
  • 使用SQLite完成增刪改查
  • 子線程操作數(shù)據(jù)庫(kù)
  • 數(shù)據(jù)庫(kù)的優(yōu)化

<br />

  • 1. 集成

  • 1.1先創(chuàng)建一個(gè)Swift 3.0工程,名字隨意师骗,創(chuàng)建完成后點(diǎn)擊項(xiàng)目工程名

添加類(lèi)庫(kù)

<br />

  • 1.2創(chuàng)建一個(gè)橋接文件

command + n


橋接文件1

橋接文件2

橋接文件3

<br />

  • 1.3導(dǎo)入頭文件

輸入 #import <sqlite3.h>

導(dǎo)入頭文件

<br />

  • 1.4 然后在ViewController中輸入sqlite3历等,有提示表示集成成功.

如圖,類(lèi)似就行

<br />

  • 在開(kāi)始之前先說(shuō)明一下:由于代碼的注釋都寫(xiě)的很詳細(xì)了,所以不會(huì)逐行解釋量窘。

<br />

  • 2. 使用SQLite完成增刪改查

要想完成對(duì)數(shù)據(jù)的增刪改查遗契,得有一個(gè)文件吧,和上一篇類(lèi)似寡夹,我們應(yīng)該先創(chuàng)建數(shù)據(jù)庫(kù)文件,然后創(chuàng)建表厂置,才能進(jìn)行增刪改查菩掏,所以我們第一步是創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)文件、然后是創(chuàng)建表昵济。然后我們創(chuàng)建一個(gè)單例類(lèi)來(lái)專(zhuān)門(mén)管理數(shù)據(jù)庫(kù)智绸。

  • 2.1 創(chuàng)建單例類(lèi)

使用 command + n 創(chuàng)建一個(gè)類(lèi)SQLManager,實(shí)現(xiàn)單例

/// 單例
    static let manager : SQLManager = SQLManager()
    class func shareInstance() -> SQLManager{
        return manager
    }

<br />

  • 2.2寫(xiě)一個(gè)方法創(chuàng)建數(shù)據(jù)庫(kù)文件

創(chuàng)建數(shù)據(jù)庫(kù)文件,首先需要一個(gè)文件名访忿,然后通過(guò)文件名得到地址瞧栗,再通過(guò)地址去創(chuàng)建數(shù)據(jù)庫(kù)文件
 /// 打開(kāi)數(shù)據(jù)庫(kù)
    func openDB(DBName: String){
        
        //1、拿到數(shù)據(jù)庫(kù)路徑
        let path = DBName.documentDir()
        //打印路徑海铆,以便拿到數(shù)據(jù)文件
        print(path)
        
        //2迹恐、轉(zhuǎn)化為c字符串
        let cPath = path.cString(using: String.Encoding.utf8)
        /*
         參數(shù)一:c字符串,文件路徑
         參數(shù)二:OpaquePointer 一個(gè)數(shù)據(jù)庫(kù)對(duì)象的地址
         
         注意Open方法的特性:如果指定的文件路徑已有對(duì)應(yīng)的數(shù)據(jù)庫(kù)文件會(huì)直接打開(kāi)卧斟,如果沒(méi)有則會(huì)創(chuàng)建在打開(kāi)
         使用Sqlite_OK判斷
         sqlite3_open(cPath, &dbBase)
         */
        /*
         #define SQLITE_OK           0   /* Successful result */
         */
        if sqlite3_open(cPath, &dbBase) != SQLITE_OK{
            print("數(shù)據(jù)庫(kù)打開(kāi)失敗")
            return
        }
    }

<br />

  • 2.3如果你運(yùn)行沒(méi)有打印錯(cuò)誤提示殴边,那么就是創(chuàng)建成功了,這個(gè)時(shí)候我們就創(chuàng)建一個(gè)表珍语,表的設(shè)計(jì)如下:

表設(shè)計(jì)如圖

有了 表的設(shè)計(jì)锤岸,我們就可以編寫(xiě)SQL語(yǔ)句了,寫(xiě)一個(gè)方法去完成表的創(chuàng)建廊酣,在創(chuàng)建表之前有一點(diǎn)我覺(jué)得有必要提一下:
在SQLite中除查詢能耻,其他的都是使用exec去執(zhí)行,所以先把exec封裝一下

 func execSQL(sql : String) -> Bool {
        // 1、先把OC字符串轉(zhuǎn)化為C字符串
        let cSQL = sql.cString(using: String.Encoding.utf8)
        
        // 2晓猛、執(zhí)行語(yǔ)句
        /// 在SQLite3中饿幅,除了查詢以外(創(chuàng)建/刪除/更新)都是用同一個(gè)函數(shù)
        /*
         1. 已經(jīng)打開(kāi)的數(shù)據(jù)庫(kù)對(duì)象
         2. 需要執(zhí)行的SQL語(yǔ)句,c字符串
         3. 執(zhí)行SQL語(yǔ)句之后的回調(diào)戒职,一般寫(xiě)nil
         4. 是第三個(gè)參數(shù)的第一個(gè)參數(shù)栗恩,一般傳nil
         5. 錯(cuò)誤信息,一般傳nil
         
         SQLITE_API int SQLITE_STDCALL sqlite3_exec(
         sqlite3*,                                  /* An open database */
         const char *sql,                           /* SQL to be evaluated */
         int (*callback)(void*,int,char**,char**),  /* Callback function */
         void *,                                    /* 1st argument to callback */
         char **errmsg                              /* Error msg written here */
         );
         */
        
        if sqlite3_exec(dbBase, cSQL, nil, nil, nil) != SQLITE_OK {
            return false
        }
        return true
    }

創(chuàng)建表

@discardableResult func createTab() -> Bool{
        
        //1洪燥、編寫(xiě)SQL語(yǔ)句
        let sql = "CREATE TABLE IF NOT EXISTS T_Person\n" +
            "(\n" +
            "id INTEGER PRIMARY KEY AUTOINCREMENT,\n" +
            "name TEXT NOT NULL,\n" +
            "age INTEGER, \n" +
            "money REAL DEFAULT 100.0\n" +
        ");"
        print(sql)
        
        let flag = execSQL(sql: sql)
        if !flag {
            print("創(chuàng)建表失敗")
        }
        return flag
    }
這里要注明一下磕秤,為什么SQL語(yǔ)句是這樣子組成的,看了打印信息就明白了
CREATE TABLE IF NOT EXISTS T_Person
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER, 
money REAL DEFAULT 100.0
);

<br />

  • 2.4 增

剛才我們?cè)O(shè)計(jì)了一個(gè)表包括id捧韵、name市咆、age、money再来,然后我們要進(jìn)行插入數(shù)據(jù)蒙兰,但是如果所有的數(shù)據(jù)都用單例來(lái)管理,如果我有5個(gè)表芒篷,并且每個(gè)月都有增刪改查搜变,那么我的單例會(huì)很臃腫,秉著睡的事情誰(shuí)干的原則针炉,這個(gè)時(shí)候我們?cè)趧?chuàng)建一個(gè)類(lèi):Person,這個(gè)類(lèi)自己去管理增刪改查挠他。

 /// 插入
    func insertSQL() -> Bool{
        
        //斷言
        assert(name != nil, "姓名不能為空")
        
        //1、編寫(xiě)SQL語(yǔ)句
        //如果插入的是可選的篡帕,那么你會(huì)發(fā)現(xiàn)你插入的前面有一個(gè)Optional殖侵,這是很尷尬的事情,
        //可以通過(guò) 赂苗! 解決
        
        var sql : String = ""
        if id == -1 {
            sql = "INSERT INTO T_Person (name,age,money) VALUES('\(name!)',\(age == -1 ? 0 : age),\(money == -1 ? 100.0 : money));"
        }
        else{
            sql = "INSERT INTO T_Person (id,name,age,money) VALUES(\(id),'\(name!)',\(age == -1 ? 0 : age),\(money == -1 ? 100.0 : money));"
        }
        //3愉耙、執(zhí)行語(yǔ)句
        return SQLManager.shareInstance().execSQL(sql: sql)
        
    }

<br />

  • 2.5 刪

/// 刪除
    func deleteSQL() -> Bool{
        
        //1、編寫(xiě)SQL語(yǔ)句
        let sql = sqlWithType(sql: "DELETE FROM T_Person", contactStr: "AND")
        print("del - \(sql)")
        
        let flag = SQLManager.shareInstance().execSQL(sql: sql)
        
        return false
    }

<br />

  • 2.6 改

/// 更新
    func updateSQL() -> Bool{
        //斷言
        assert(name != nil, "姓名不能為空")
        
        //1拌滋、編寫(xiě)SQL語(yǔ)句
        let sql = "UPDATE T_Person \n" +
                    "SET name='\(name!)',age=\(age),money=\(money) \n" +
                    "WHERE id=\(id);";
        
        //3、執(zhí)行語(yǔ)句
        return SQLManager.shareInstance().execSQL(sql: sql)
    }

<br />

  • 2.7 查

class func selectSQL(sql : String) -> [Person]{
        //2猜谚、獲取查詢的數(shù)據(jù)
        let dicts = SQLManager.shareInstance().selectSQL(sql: sql)
        //3败砂、創(chuàng)建一個(gè)數(shù)組用于保存模型
        var datas = [Person]()
        //4、遍歷字典數(shù)組魏铅,生成模型數(shù)組
        for dict in dicts{
            datas.append(Person(dict: dict))
        }
        //5昌犹、返回模型數(shù)組
        return datas
    }

<br />

3、完成界面搭建:

項(xiàng)目結(jié)構(gòu)圖

圖片.png

作為主入口ViewController包括了一個(gè)Toolbar览芳、一個(gè)TableView斜姥、一個(gè)右按鈕
其中toolBar又連接著增、刪铸敏、改缚忧、查、排序杈笔、分頁(yè)等界面的入口闪水,在其他中則是一些數(shù)據(jù)庫(kù)的優(yōu)化(插入大量數(shù)據(jù)的時(shí)候如何優(yōu)化)。

ViewController

<br />

3.1 增

界面包括四個(gè)textfiled,一個(gè)button,一個(gè)label,一個(gè)navigation item

  • textfiled用來(lái)給用戶輸入數(shù)據(jù)
  • button 用來(lái)執(zhí)行插入操作
  • label用來(lái)顯示插入狀態(tài)
  • navigation item用于快速插入數(shù)據(jù)蒙具,便于測(cè)試
增加

插入按鈕代碼如下:
這里需要注意的是:類(lèi)型的判斷球榆,ID,age必須是Int類(lèi)型禁筏,name 必須是String持钉、而money必須是浮點(diǎn)型 Double,所以在取值的時(shí)候需要進(jìn)行判斷

 @IBAction func insertData(_ sender: UIButton) {
        //名字為空 不能插入
        if nameTextfield.text?.characters.count == 0 {
            status.text = statuText + "姓名不能為空"
            return
        }

        //通過(guò)textField創(chuàng)建對(duì)象
        var dict = [String : Any]()
        //如果id輸入了篱昔,就插入ID每强,否則自增長(zhǎng)
        if idTextfield.text?.characters.count != 0{
            if idTextfield.text!.isAllNum() { //判斷是不是純數(shù)字
                dict["id"] = (idTextfield.text! as NSString).intValue
            }
        }
        
        //名字直接添加
        dict["name"] = nameTextfield.text!
        
        //如果輸入了年齡并且是純數(shù)字才添加
        if ageTextfield.text?.characters.count != 0 {
            if ageTextfield.text!.isAllNum() { //判斷是不是純數(shù)字
                dict["age"] = (ageTextfield.text! as NSString).intValue
            }
        }
        
        
        if moneyTextfield.text?.characters.count != 0 {
            let double : Double = (moneyTextfield.text! as NSString).doubleValue
            if double > 0{
                dict["money"] = double
            }
        }
        
        let p = Person(dict: dict)
        if !p.insertSQL() {
            status.text = statuText + "插入失敗"
        }else{
            status.text = statuText + "插入成功"
        }
    }

<br />

3.2 刪

界面包括四個(gè)textfiled,一個(gè)button,一個(gè)label,

  • textfiled用來(lái)給用戶輸入數(shù)據(jù)
  • button 用來(lái)執(zhí)行插入操作
  • label用來(lái)顯示插入狀態(tài)
刪除

刪除按鈕代碼如下:
這里需要注意的是條件的拼接、當(dāng)沒(méi)有輸入條件的時(shí)候默認(rèn)把整個(gè)表所有數(shù)據(jù)刪除旱爆,這里我們自定義了一個(gè)字典舀射,前面是一個(gè)結(jié)構(gòu)體,后面是一個(gè)可選的值怀伦,因?yàn)橛脩艨赡茌斎?/code>

@IBAction func deleteData(_ sender: UIButton) {
        
        
        var dict = [Person_Property : Any?]()
        dict[.id] = idTextfield.text
        dict[.name] = nameTextfield.text
        dict[.age] = ageTextfield.text
        dict[.money] = moneyTextfield.text
        
        let p = Person(myDict : dict)
        
        if p.deleteSQL(){
            statuLabel.text = statuText + "刪除成功"
        }
        else{
            statuLabel.text = statuText + "刪除失敗"
        }
        

    }

構(gòu)造方法如下:

init(myDict : [Person_Property : Any?]){
        super.init()
        
        var dict = [String : Any]()
        
        for (key,value) in myDict {
            if (value as! String).characters.count > 0 {
                if key.rawValue as String == "id" {
                    if (value as! String).isAllNum() { //并且全是數(shù)字
                        dict[key.rawValue as String] = Int((value as! NSString).intValue)
                    }
                }else if key.rawValue as String == "name" {
                    dict[key.rawValue as String] = value
                    
                }else if key.rawValue as String == "age" {
                    if (value as! String).isAllNum() { // 并且全是數(shù)字
                        dict[key.rawValue as String] = Int((value as! NSString).intValue)
                    }
                }else if key.rawValue as String == "money" { //
                    if (value as! String).isFloatValue() { //并且是浮點(diǎn)型
                        dict[key.rawValue as String] = (value as! NSString).doubleValue
                    }
                }
            }
        }
        
        setValuesForKeys(dict)
        
    }

<br />

3.3 改

界面包括一個(gè)現(xiàn)實(shí)所有數(shù)據(jù)的tableView和一個(gè)二級(jí)頁(yè)面脆烟,點(diǎn)擊cell進(jìn)入二級(jí)頁(yè)面,修改特定的數(shù)據(jù)

頁(yè)面一
這里我并沒(méi)有把ID也列進(jìn)來(lái)房待,因?yàn)楫?dāng)數(shù)據(jù)量達(dá)到一定量的時(shí)候如果你貿(mào)然去修改ID邢羔,并且這個(gè)ID是主鍵,
很有可能會(huì)造成主鍵沖突桑孩,所以ID就一般原則而言要唯一拜鹤,且不修改

這里包括一個(gè)關(guān)閉按鈕、界面采用present的方式顯示流椒,一個(gè)當(dāng)前用戶信息的label以及三個(gè)提供給用戶輸入的textfield敏簿,一個(gè)button以及一個(gè)更新?tīng)顟B(tài)的顯示

頁(yè)面二

<br />

3.4 查 、3.5 排序

由于這兩個(gè)非常類(lèi)似宣虾,界面布局都一致惯裕,所以就都拿到一起了
包括一個(gè)label、一個(gè)textView绣硝、一個(gè)button蜻势、一個(gè)tableView

<br />


查找、排序

查找Code

 class func queryPersons(condition : String) -> [Person]{
        //如果輸入的為空鹉胖,就全部加載
        if condition == ""{
            return loadPersons()
        }
        
        //1握玛、編寫(xiě)SQL語(yǔ)句
        let sql = "SELECT * FROM T_Person WHERE \(condition);"
        
       return selectSQL(sql: sql)
        
    }

排序Code

     /// 排序
    ///
    /// - Parameter sort: 字段
    /// - Returns: 數(shù)組
    class func querySortPersons(sort : String) -> [Person]{
        //如果輸入的為空够傍,就全部加載
        if sort == "" {
            return loadPersons()
        }
        
        //1、編寫(xiě)SQL語(yǔ)句
        let sql = "SELECT * FROM T_Person ORDER BY \(sort);"
        
        return selectSQL(sql: sql)
    }

<br />

3.6 分頁(yè)檢索數(shù)據(jù)

這個(gè)界面也很簡(jiǎn)單挠铲,主要包括一個(gè)textField冕屯、三個(gè)Button、一個(gè)TableView

  • textField 用于輸入每頁(yè)顯示的條目數(shù)
  • 三個(gè)按鈕分別作用于:開(kāi)始所有市殷、上/下一頁(yè)
  • tableView主要用于顯示數(shù)據(jù)

<br />


LIMIT

檢索代碼愕撰,通過(guò)傳入的m,n進(jìn)行搜索醋寝,如果還不是很清楚的可以去我的上一篇簡(jiǎn)書(shū)文章查看搞挣。

class func queryLimitPerson(m : Int32, n : Int32) -> [Person]{
        if n == 0 {
            return loadPersons()
        }
        
        let sql = "SELECT * FROM T_Person LIMIT \(m),\(n)"
        
        return selectSQL(sql: sql)
    }

三個(gè)按鈕code

 private func limitDatas(m : Int32, n:Int32){
        let pdatas = Person.queryLimitPerson(m : m , n: n)
        if pdatas.count == 0{
            showErrorText()
            return
        }
        
        datas = pdatas
    }
    
    @IBAction func startLimitBtn(_ sender: UIButton) {
        index = 0
        limitDatas(m: Int32(index) * (textField.text! as NSString).intValue, n: (textField.text! as NSString).intValue)
    }
    
    @IBAction func nextBtn(_ sender: Any) {
        index += 1
        limitDatas(m: Int32(index) * (textField.text! as NSString).intValue, n: (textField.text! as NSString).intValue)
    }
    
    @IBAction func preBtn(_ sender: Any) {
        index = (index - 1) < 0 ? 0 : (index - 1)
        limitDatas(m: Int32(index) * (textField.text! as NSString).intValue, n: (textField.text! as NSString).intValue)
    }

4、事務(wù)音羞,預(yù)編譯囱桨、線程安全

4.1 事務(wù):

事務(wù)(Transaction)是一個(gè)對(duì)數(shù)據(jù)庫(kù)執(zhí)行工作單元。事務(wù)(Transaction)是以邏輯順序完成的工作單位或序列嗅绰,可以是由用戶手動(dòng)操作完成舍肠,也可以是由某種數(shù)據(jù)庫(kù)程序自動(dòng)完成。

事務(wù)(Transaction)是指一個(gè)或多個(gè)更改數(shù)據(jù)庫(kù)的擴(kuò)展窘面。例如翠语,如果您正在創(chuàng)建一個(gè)記錄或者更新一個(gè)記錄或者從表中刪除一個(gè)記錄,那么您正在該表上執(zhí)行事務(wù)财边。重要的是要控制事務(wù)以確保數(shù)據(jù)的完整性和處理數(shù)據(jù)庫(kù)錯(cuò)誤肌括。

使用下面的命令來(lái)控制事務(wù):
BEGIN TRANSACTION:開(kāi)始事務(wù)處理。

COMMIT:保存更改酣难,或者可以使用 END TRANSACTION 命令谍夭。

ROLLBACK:回滾所做的更改。

事務(wù)控制命令只與 DML 命令 INSERT憨募、UPDATE 和 DELETE 一起使用紧索。他們不能在創(chuàng)建表或刪除表時(shí)使用,因?yàn)檫@些操作在數(shù)據(jù)庫(kù)中是自動(dòng)提交的菜谣。

<br />

  • 然后我們?cè)赟QLManager中添加事務(wù)代碼
    /// 開(kāi)啟事務(wù)
    func beginTransaction(){
        execSQL(sql: "BEGIN TRANSACTION")
    }
    
    /// 提交事務(wù)
    func commitTransaction(){
        execSQL(sql: "COMMIT TRANSACTION")
    }
    
    /// 回滾
    func rollbackTransaction(){
        execSQL(sql: "ROLLBACK TRANSACTION")
    }

<br />

4.2 預(yù)編譯

關(guān)于預(yù)編譯的意思珠漂,網(wǎng)上很多種,我這里打一個(gè)比喻:
你是一個(gè)司機(jī)尾膊,今天下午要去倉(cāng)庫(kù)A里面去那2000件貨物甘磨。
預(yù)編譯:你打電話給倉(cāng)庫(kù),讓倉(cāng)庫(kù)管理員給你準(zhǔn)備好貨物眯停,然后你準(zhǔn)時(shí)到達(dá),裝貨走人卿泽。
不是預(yù)編譯:你下午三點(diǎn)到了倉(cāng)庫(kù)莺债,再讓倉(cāng)庫(kù)管理員給你準(zhǔn)備貨物滋觉,清點(diǎn)完成后再裝貨,在走人齐邦。
所以預(yù)編譯是可以提高效率的椎侠。

預(yù)編譯執(zhí)行語(yǔ)句:

    @discardableResult func batchExecSQL(sql : String , args: CVarArg...) -> Bool{
        //1轉(zhuǎn)化為C字符串
        let cSql = sql.cString(using: String.Encoding.utf8)!
        
        //2措拇、執(zhí)行預(yù)編譯
        var stmt : OpaquePointer? = nil
        if  sqlite3_prepare_v2(dbBase, cSql, -1, &stmt, nil) != SQLITE_OK {
            print("預(yù)編譯失敗")
            sqlite3_finalize(stmt)
            return false
        }
        
        //3我纪、進(jìn)行數(shù)據(jù)綁定
        /*
         這里要注意,下標(biāo)從1開(kāi)始丐吓。
         */
        var index : Int32 = 1
        /*
         sqlite3_bind_XX(句柄, 下標(biāo)(從1開(kāi)始), 值)
         */
        for objc in args{
            if objc is Int{
                sqlite3_bind_int64(stmt, index, sqlite3_int64(objc as! Int))
            }else if objc is Double{
                sqlite3_bind_double(stmt, index, objc as! Double)
            }else if objc is String{
                //得到字符串
                let text = objc as! String
                //得到C字符串
                let cText = text.cString(using: String.Encoding.utf8)!
                /*
                 sqlite3_bind_text(句柄, 下標(biāo), 字符串, 字符串長(zhǎng)度 -1 表示系統(tǒng)自己計(jì)算, OC傳入nil浅悉,SWIFT不行)
                 1 句柄
                 2 下標(biāo)
                 3 C字符串
                 4 C字符串長(zhǎng)度 -1 自動(dòng)計(jì)算
                 5 OC 傳入nil 但是SWIFT不行,因?yàn)閷?duì)象提前釋放掉了券犁,會(huì)導(dǎo)致插入的數(shù)據(jù)不對(duì)
                    typedef void (*sqlite3_destructor_type)(void*);
                 
                    #define SQLITE_STATIC      ((sqlite3_destructor_type)0)
                    #define SQLITE_TRANSIENT   ((sqlite3_destructor_type)-1)
                 
                    第五個(gè)參數(shù)如果傳入SQLITE_STATIC/nil, 那么系統(tǒng)不會(huì)保存需要綁定的數(shù)據(jù), 如果需要綁定的數(shù)據(jù)提前釋放了, 那么系統(tǒng)就隨便綁定一個(gè)值
                    第五個(gè)參數(shù)如果傳入SQLITE_TRANSIENT, 那么系統(tǒng)會(huì)對(duì)需要綁定的值進(jìn)行一次copy, 直到綁定成功之后再釋放
                 
                 但是Swift中并不能直接寫(xiě) SQLITE_TRANSIENT 或者 -1术健,需要自定義一個(gè)SQLITE_TRANSIENT,來(lái)覆蓋系統(tǒng)的
                 在 124 行中
                 */
                sqlite3_bind_text(stmt, index, cText, -1, SQLITE_TRANSIENT)
            }
            index += 1
        }
        
        
        //4粘衬、執(zhí)行SQL語(yǔ)句
        if sqlite3_step(stmt) != SQLITE_DONE {
            print("執(zhí)行SQL語(yǔ)句失敗")
            return false
        }
        
        //5荞估、重置STMT
        if sqlite3_reset(stmt) != SQLITE_OK{
            print("重置句柄失敗")
            return false
        }
        
        //6、關(guān)閉STMT
        sqlite3_finalize(stmt)
        
        return true
    }

<br />

4.2 線程安全

在多線程中操作數(shù)據(jù)庫(kù)是有安全隱患的稚新,可能會(huì)發(fā)生這里在執(zhí)行插入勘伺、另外又在執(zhí)行刪除、更新或者其他的指令褂删,所以多線程操作數(shù)據(jù)庫(kù)一定要保證線程安全飞醉。
如何保證呢?

  • 通過(guò)創(chuàng)建一個(gè)創(chuàng)行隊(duì)列

多線程執(zhí)行操作模式

//    MARK - Child Thread
    /*
     1 一個(gè)唯一的對(duì)列名
     2 優(yōu)先級(jí)
     3 隊(duì)列類(lèi)型
     4 
     */
    private let dbQueue = DispatchQueue(label: "com.codepgq.github", qos: DispatchQoS.default, attributes: DispatchQueue.Attributes.concurrent, autoreleaseFrequency: DispatchQueue.AutoreleaseFrequency.inherit, target: nil)
    //DispatchQueue(label:"com.appcoda.queue2", qos:DispatchQoS.userInitiated)
    func execQueueSQL(action : @escaping (_ manager : SQLManager) ->()){
        //開(kāi)一個(gè)子線程
        DispatchQueue.global().async { 
            action(self)
        }
    }

或者通過(guò)這種方式創(chuàng)建串行隊(duì)列

// 創(chuàng)建一個(gè)串行隊(duì)列
    fileprivate let dbQueue = DispatchQueue(label: "com.codepgq.github", attributes: [])

4.4OK笤妙,了解了上面的知識(shí)冒掌,我們就搭建最后一個(gè)頁(yè)面:

界面很簡(jiǎn)單,就不做介紹了蹲盘,主要是通過(guò)判斷打開(kāi)了哪些switch來(lái)進(jìn)行芳芳的選擇股毫。

其他

<br />
所有的方法如下:

    @IBAction func startInsert(_ sender: Any) {
        
        if isSerting {
            showErrorText(message : "正在插入")
            return
        }
        isSerting = true
        
        //計(jì)算值
        let value : Int8 = isOnValue(sw: openTrans) * 100 + 10 * isOnValue(sw: openThread) + isOnValue(sw: openPrepare)
        
        print(NSString.init(format: "value - %03d", value))
        /*
         001 010 100 110 101 011 000 111
         */
        switch value {
        case 001:
            //開(kāi)啟了預(yù)編譯
            insertDatas(true)
        case 010:
            //開(kāi)啟了子線程
            openChildThread()
        case 100:
            //開(kāi)啟了事務(wù)
            openTransaction()
        case 011:
            //開(kāi)啟了子線程 預(yù)編譯
            openTheadAndPrepare()
        case 110:
            //開(kāi)啟了事務(wù) 子線程
            openTransAndTheard()
        case 101:
            //開(kāi)啟了事務(wù) 預(yù)編譯
            openTransAndPrepare()
        case 111:
            //開(kāi)啟了事務(wù) 子線程 預(yù)編譯
            openAll()
        default:
            //啥都沒(méi)開(kāi)
            insertDatas(false)
        }
        
    }
    
    /// 開(kāi)啟事務(wù)和預(yù)編譯
    func openTransAndPrepare(){
        SQLManager.shareInstance().beginTransaction()
        insertDatas(true)
        SQLManager.shareInstance().commitTransaction()
    }
    
    /// 開(kāi)啟事務(wù)和線程
    func openTransAndTheard(){
        SQLManager.shareInstance().execQueueSQL { (manager) in
            self.insertDatas(false)
        }
    }
    
    //開(kāi)啟線程和預(yù)編譯
    func openTheadAndPrepare(){
        SQLManager.shareInstance().execQueueSQL { (manager) in
            self.insertDatas(true)
        }
    }
    
    //全部打開(kāi)
    func openAll(){
        SQLManager.shareInstance().execQueueSQL { (manager) in
            manager.beginTransaction()
            self.insertDatas(true)
            manager.commitTransaction()
        }
    }
    
    //開(kāi)啟子線程
    func openChildThread(){
        SQLManager.shareInstance().execQueueSQL { (manager) in
            self.insertDatas(false)
        }
    }
    
    //開(kāi)啟事務(wù)
    func openTransaction(){
        //獲取數(shù)據(jù)庫(kù)對(duì)象
        let manager = SQLManager.shareInstance()
        //開(kāi)始事務(wù)
        manager.beginTransaction()
        //插入數(shù)據(jù)
        insertDatas(false)
        //提交事務(wù)
        manager.commitTransaction()
    }
    
    //插入數(shù)據(jù)
    private func insertDatas(_ prepare : Bool) {
        //得到開(kāi)始時(shí)間
        let start = CFAbsoluteTimeGetCurrent()
        startLabel.text = "開(kāi)始時(shí)間:\(start)"
        
        print(#function,"\(prepare ? "預(yù)編譯" : "未開(kāi)啟預(yù)編譯" )")
        
        //開(kāi)始插入
        
        for index in 0..<insertCount {
            let name = "rand\(index)"
            let age = Int(arc4random() % 100 + 1)
            let money = Double(arc4random() % 10000) + Double(arc4random() % 100) * 0.01
            if prepare {
                //預(yù)編譯
                let sql = "INSERT INTO T_Person (name,age,money) VALUES(?,?,?);"
                SQLManager.shareInstance().batchExecsql(sql, args: name,age,money)
            }
            else{
                //直接插入
                let sql = "INSERT INTO T_Person (name,age,money) VALUES('\(name)',\(age),\(money));"
                SQLManager.shareInstance().execSQL(sql: sql)
            }
            
        }
        
        
        //得到結(jié)束時(shí)間
        let end = CFAbsoluteTimeGetCurrent()
        endLabel.text = "結(jié)束時(shí)間:\(end)"
        
        //得出耗時(shí)
        timeLabel.text = "耗時(shí):\(end - start)"
        
        isSerting = false
        
    }
    
    //計(jì)算當(dāng)前是不是開(kāi)啟狀態(tài)
    private func isOnValue(sw : UISwitch) -> Int8{
        return sw.isOn ? 1 : 0
    }

<br />
什么都沒(méi)開(kāi)啟的情況下插入10000條數(shù)據(jù):


什么都沒(méi)開(kāi)啟

<br />
開(kāi)啟了預(yù)編譯的情況下插入10000條數(shù)據(jù):


開(kāi)了預(yù)編譯

<br />
開(kāi)啟了事務(wù)的情況下插入10000條數(shù)據(jù):


開(kāi)啟了事務(wù)

<br />
開(kāi)啟了預(yù)編譯和事務(wù)的情況下插入10000條數(shù)據(jù):


開(kāi)啟了事務(wù)和預(yù)編譯

開(kāi)啟了線程的時(shí)候就不會(huì)阻塞UI,可自行測(cè)試召衔。

Demo傳送門(mén)

寫(xiě)的不好見(jiàn)諒铃诬,但是如果對(duì)你有幫助,那么我就心滿意足了苍凛。
碼字不易趣席、喜歡點(diǎn)一下。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末醇蝴,一起剝皮案震驚了整個(gè)濱河市宣肚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌悠栓,老刑警劉巖霉涨,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件按价,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡笙瑟,警方通過(guò)查閱死者的電腦和手機(jī)楼镐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)往枷,“玉大人框产,你說(shuō)我怎么就攤上這事〈斫啵” “怎么了秉宿?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)墓臭。 經(jīng)常有香客問(wèn)我蘸鲸,道長(zhǎng),這世上最難降的妖魔是什么窿锉? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任酌摇,我火速辦了婚禮,結(jié)果婚禮上嗡载,老公的妹妹穿的比我還像新娘窑多。我一直安慰自己,他們只是感情好洼滚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布埂息。 她就那樣靜靜地躺著,像睡著了一般遥巴。 火紅的嫁衣襯著肌膚如雪千康。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天铲掐,我揣著相機(jī)與錄音拾弃,去河邊找鬼。 笑死摆霉,一個(gè)胖子當(dāng)著我的面吹牛豪椿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播携栋,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼搭盾,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了婉支?” 一聲冷哼從身側(cè)響起鸯隅,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎向挖,沒(méi)想到半個(gè)月后滋迈,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體霎奢,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年饼灿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片帝美。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碍彭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出悼潭,到底是詐尸還是另有隱情庇忌,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布舰褪,位于F島的核電站皆疹,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏占拍。R本人自食惡果不足惜略就,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望晃酒。 院中可真熱鬧表牢,春花似錦、人聲如沸贝次。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蛔翅。三九已至敲茄,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間山析,已是汗流浹背堰燎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盖腿,地道東北人爽待。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像翩腐,于是被迫代替她去往敵國(guó)和親鸟款。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容