iOS 移動儲存初探(一):SQLite

PeachRain發表於2017-12-14

首先我關於移動端的儲存,其實有很多的解決方案,ios端>可以使phlist,UserDefaults,歸檔,當然還有很多人覺得>難用的CoreData,當然最重要的還有SQLite,SQLite設計>之初就是為了解決小型資料庫使用的,輕,便,快。所以 當然蘋果中也可以使用SQLite,所以蘋果公司就為我們封 裝了,CoreData這個api為了方便開發者的使用,但是有很多人卻覺得不怎麼好用,也可能是因為,對接觸過資料庫操作的人來說相應的查詢等語句會更加的親切好用。

這裡我就不討論CoreData了,因為最近正在研究CoreData,因為蘋果自己很多的應用使用的是CoreData,我覺得難用的東西一但會用了,就能夠事半功倍了。


不多這次就先說下SQLite--一個普遍的移動端資料持久化資料庫。

首先說下SQLite: 雖然蘋果為我們提供了CoreData但我們也可以使用比較低層的SQLite. 在swift中如何使用呢?(swift3.0) 首先也要引入庫libsqlite3.0.tbd由於swift中要使用oc先要引入標頭檔案。所以這次我就用橋接檔案直接把所有的引用放在橋接檔案.h中

SQLite

這樣我們就能直接在swift使用SQLite了。在使用之前,基本的SQLite語法還是要了解的,這個只要百度,谷歌搜尋就能找到很多,我在這裡只是介紹一些比較常用的

//OpaquePointer: *db,資料庫控制程式碼,跟檔案控制程式碼FIFL類似,這裡是sqlite3指標;
//sqlite3_stmt: *stmt,相當於ODBC的Command物件,用於儲存編譯好的SQL語句;
//sqlite3_open(): 開啟資料庫,沒有資料庫時建立;
//sqlite3_exec(): 執行非查詢的SQL語句;
//sqlite3_step(): 在呼叫sqlite3_prepare後,使用這個函式在記錄集中移動;
//sqlite3_close():關閉資料庫檔案;
//sqlite3_column_text():取text型別的資料;
//sqlite3_column_blob():取blob型別的資料;
//sqlite3_column_int():取int型別的資料
複製程式碼

在OC中可以使用sqlite3_stmt*指標運算元據庫,在swift中由於基本不使用指標,但是我們可以使用OpaquePointer作為替代,OpaquePointer是swift中不透明指標,當然swift中還有UnsafePointer這種明確準確的指標可以使用,但在這裡他們不是重點。

由於大名鼎鼎的FMDB在開發者中的廣泛使用,使我們能夠減少避免使用更低層的語言來運算元據庫和使用。但是作為一名沒事找事的程式設計師還是想要使用自己的類庫,所以就稍微看了看FMDB的原始碼再結合一些資料庫的知識,給自己寫了個SQLite的Manager類,方便以後運算元據庫。

首先資料庫都是儲存在本地的沙盒裡的,我們可以給自己建立一個資料庫檔名字一般是“XXX.SQLite”當然建立一次就夠了,接著再使用之前還是需要開啟的不然就不能使用了。同時為了方便使用也寫把Manager分裝成了單例

extension1.png

extension2.png

sqlite2.png

開啟資料庫也可以使用sqlite3_open來開啟資料庫,但是有時會報錯,error: only read為了確保資料庫的可讀寫,就使用sqlite3_open_v2來開啟和配置資料庫為READWRITE(可讀寫)

資料庫已經有了,可是沒有表格也是不能儲存資料的,接下來就是建表

sqlite3.png

這裡會做一些判斷,主要是資料庫有沒有判斷還有表格已存在的判斷 現在表格有了,之後想幹嘛就能幹嘛了,哈哈哈 接著是表格的增刪改查 可以使用sqlite3_exec()來執行,但是效率會低點,因為sqlite3_exec()會反覆的執行sqlite3_prepare_v2,sqlite3_step等所以效率會低,所以我們直接使用sqlite3_prepare_v2,sqlite3_step 首先是插入操作,由於每個人的想法思路不同,程式碼結構也會不同 語句判斷(臨時寫的還需要大改)

func prepareForSQLite(type:SQLliteExecType,paramDic:[String:Any]?,tableName:String)->OpaquePointer?{
        
        if !existsTable(name: tableName) {
            debugPrint("表格不存在")
            return nil
        }
        var stmt : OpaquePointer? = nil
        
        guard let params = paramDic else{
            return nil
        }
        if params.keys.count < 0{
            return nil
        }
        
        // 1.獲取建立表的SQL語句
        let querySQL = arrayToString(type:type,paramDic: paramDic!, tableName: tableName)
        
        var rc:Int32? = nil
        if !(stmt != nil) {
            rc = sqlite3_prepare_v2(p, querySQL.codeUTF8(),-1, &stmt, nil)
            if rc != SQLITE_OK {
                debugPrint("新增過程錯誤")
                sqlite3_finalize(stmt)
                return nil
            }
        }
        var idx = 0
        let queryCount = sqlite3_bind_parameter_count(stmt);
        for (key,value) in params {
            let parameterName = ":\(key)"
            let namedIdx = sqlite3_bind_parameter_index(stmt, parameterName.codeUTF8());
            if (namedIdx > 0) {
                // 在這裡繫結
                self.bindObjectByIndexWithP(obj: value, ndx: namedIdx, stmt: stmt!)
                idx+=1;
            }
            else {
                debugPrint("Could not find index for \(key)")
            }
        }
        _ = stepSQL(sql: querySQL, stmt: stmt!)
        
        if (idx != Int(queryCount)) {
            print("Error: the bind count is not correct (executeQuery)")
            sqlite3_finalize(stmt)
        }
        return stmt
    }
複製程式碼
private func arrayToString(type:SQLliteExecType,paramDic:[String:Any],tableName:String)->String{
        switch type {
        case .insert:
            let values = paramDic.keys
            let keys = paramDic.keys.joined(separator: ",")
            let valuess = values.map { (str) -> String in
                return ":\(str)"
                }.joined(separator: ",")
            let querySQL = "INSERT OR REPLACE INTO \(tableName)(\(keys)) VALUES(\(valuess));"
            return querySQL
        case .delete:
            let querySQL = "DELETE FROM \(tableName);"
            return querySQL
        case .update:
            return ""
        default:
            let querySQL = "SELECT * FROM \(tableName);"
            return querySQL
        }
    }
複製程式碼
//MARK:<=======資料bind=======>
    //MARK:繫結資料
    ///
    /// - Parameters:
    ///   - obj: 資料
    ///   - index: 位置
    ///   - stmt: 指標sqlite3_stmt物件
    private func bindObjectByIndexWithP(obj:Any,ndx:Int32,stmt:OpaquePointer) {
        var flag:CInt = 0
        if let txt = obj as? String {
            flag = sqlite3_bind_text(stmt, CInt(ndx), txt, -1, SQLITE_TRANSIENT)
        } else if let data = obj as? NSData {
            flag = sqlite3_bind_blob(stmt, CInt(ndx), data.bytes, CInt(data.length), SQLITE_TRANSIENT)
        } else if let date = obj as? Date {
            let txt = "\(date.timeIntervalSince1970)"
            flag = sqlite3_bind_text(stmt, CInt(ndx), txt, -1, SQLITE_TRANSIENT)
        } else if let val = obj as? Bool {
            let num = val ? 1 : 0
            flag = sqlite3_bind_int(stmt, CInt(ndx), CInt(num))
        } else if let val = obj as? Double {
            flag = sqlite3_bind_double(stmt, CInt(ndx), CDouble(val))
        } else if let val = obj as? Int {
            flag = sqlite3_bind_int(stmt, CInt(ndx), CInt(val))
        } else {
            flag = sqlite3_bind_null(stmt, CInt(ndx))
        }
        if flag != SQLITE_OK {
            sqlite3_finalize(stmt)
        }
        
    }
複製程式碼

程式碼有點亂,但是基本的思路是

sqlite3_prepare_v2 準備 sqlite3_bind_XXX 繫結資料 sqlite3_step 單步執行 sqlite3_finalize 釋放操作

只要學會了,在配合order by , limit , offfset 等的SQLite語句基本可以使適用全部的操作,這裡不再做過多的闡述,直接上程式碼(增刪改查都有,具體怎麼用還要看程式碼的習慣和工程的構造) 程式碼比較亂,其中也附帶了很多其他有用的我自己整理的寫的一些方法函式等的extension喜歡的可以用,順便點下star. github:https://github.com/taosiyu/RainSQLiteDB

相關文章