iOS swift版 SQLite3 詳解

IOS中的SQLite3的封裝與詳細(xì)應(yīng)用

SQLite是一個開源的嵌入式關(guān)系數(shù)據(jù)庫,特點是易使用、高效、安全可靠、可移植性強。

IOS中的本地持久化存儲

NSUserDefault:一般用于存儲小規(guī)模數(shù)據(jù)、業(yè)務(wù)邏輯弱的數(shù)據(jù)。

keychain: 蘋果提供的可逆存儲,因為有著只要app不重裝系統(tǒng)、可以同步iCloud的特性,一般用來對用戶的標(biāo)識符或者一些需要加密的小數(shù)據(jù)進行存儲。

歸檔:主要原理是對數(shù)據(jù)進行序列化、反序列化操作后,寫入、讀出數(shù)據(jù)。方便便捷易使用,缺點查詢、更改數(shù)據(jù)耗時耗性能。

數(shù)據(jù)庫:主要的有三種sqlite3、core data、realm。其中core data只是xcode對sqlite的界面化的封裝原理相似,realm官方文檔.

關(guān)于sqlite本文將主要介紹。

SQLite3中主要函數(shù)介紹

sqlite3_open(文件路徑,sqlite3 **):文件名若不存在,則會自動創(chuàng)建

sqlite3_close(sqlite3 *):關(guān)閉數(shù)據(jù)庫

sqlite3__finalize(sqlite3_stmt *pStmt): 釋放數(shù)據(jù)庫

sqlite3_errmsg(sqlite3*):輸出數(shù)據(jù)庫錯誤

sqlite3__exec(sqlite3 *,const char *sql, sqlite3_callback,void *,char **errmsg):

參數(shù)1:open函數(shù)得到的指針。

參數(shù)2:一條sql語句

參數(shù)3:sqlite3_callback是回調(diào),當(dāng)這條語句執(zhí)行后,sqlite3會調(diào)用你提供的這個函數(shù),回調(diào)函數(shù)

參數(shù)4:void *是自己提供的指針,可以傳遞任何指針到這里,這個參數(shù)最終會傳到回調(diào)函數(shù)里面,如果不需要傳到回調(diào)函數(shù)里面,則可以設(shè)置為NULL

參數(shù)5:錯誤信息,當(dāng)執(zhí)行失敗時,可以查閱這個指針

sqlite3_prepare_v2(sqlite3 *db,const char *zSql, int nByte,sqlite3_stmt **ppStmt,const char **pzTail):

參數(shù)3:表示前面sql語句的長度,如果小于0,sqlite會自動計算它的長度

參數(shù)4:sqlite3_stmt指針的指針,解析后的sql語句就放在該結(jié)構(gòu)里

參數(shù)5:一般設(shè)為0

sqlite3_step(sqlite3_stmt*):

參數(shù)為sqlite3_prepare_v2中的sqlite3_stmt
返回SQLITE_ROW 表示成功

sqlite3_bind_text(sqlite3_stmt, int, const char, int n, void()(void)):

參數(shù)1:sqlite3_prepare_v2中的sqlite3_stmt

參數(shù)2:對應(yīng)行數(shù)

參數(shù)3:對應(yīng)行數(shù)的值

參數(shù)4:對應(yīng)行數(shù)的值的長度,小于0自動計算長度

參數(shù)5:函數(shù)指針,主要處理特殊類型的析構(gòu)

sqlite3_key( sqlite3 *db, const void *pKey, int nKey)

參數(shù)2:密鑰

參數(shù)3:密鑰長度

swift與c的類型轉(zhuǎn)換

int => CInt

char => CChar / CSignedChar

char* => CString

unsigned long = > CUnsignedLong

wchar_t => CWideChar

double => CDouble

T* => CMutablePointer

void* => CMutableVoidPointer

const T* => CConstPointer

const void* => CConstVoidPointer

等等
參考地址

創(chuàng)建或者打開數(shù)據(jù)庫

程序中‘db’不能為空,如果為空,表示打開數(shù)據(jù)庫失敗或者關(guān)閉了數(shù)據(jù)庫。

@discardableResult   private func openDB() -> Bool{
    
    if db == nil {
        let path = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/\(DB_NAME)"
        print(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])
        if sqlite3_open(path.cString(using: String.Encoding.utf8)!,&db) != SQLITE_OK {
            closeDb()
            return false
        }else {
            //對數(shù)據(jù)庫進行加密
            sqlite3_key(db, SAFE_KEY.cString(using: String.Encoding.utf8), Int32(SAFE_KEY.characters.count))
        }
        sqlite3_busy_handler(db, { (ptr,count) in
            
            usleep(500000)//如果獲取不到鎖,表示數(shù)據(jù)庫繁忙,等待0.5秒
            print("sqlite is locak now,can not write/read.")
            return 1   //回調(diào)函數(shù)返回值為1,則將不斷嘗試操作數(shù)據(jù)庫。
            
        }, &db)
    }
        
        
        return true
    }

通過sql執(zhí)行數(shù)據(jù)庫操作

由于防止多線程操作數(shù)據(jù)庫,每次執(zhí)行數(shù)據(jù)庫操作添加同步鎖。
sqlite3_exec函數(shù)基本上支持所有的數(shù)據(jù)庫執(zhí)行語句除了含有特殊類型的數(shù)據(jù)(二進制),含有特殊類型的數(shù)據(jù)會采用另一種方式處理下面會闡述。

  @discardableResult public  func execSql(sql:String)->Bool {
        
        objc_sync_enter(self)
        if  !self.openDB() {
        objc_sync_exit(self)
        return false
        }
        var err: UnsafeMutablePointer<Int8>? = nil
        if sqlite3_exec(db,sql.cString(using: String.Encoding.utf8)!,nil,nil,&err) != SQLITE_OK {
            if let error = String(validatingUTF8:sqlite3_errmsg(db)) {
               print("execute failed to execute  Error: \(error)")
            }
            objc_sync_exit(self)
            return false
        }

        objc_sync_exit(self)
        return true
    }


查詢數(shù)據(jù)庫

按照數(shù)據(jù)庫的行數(shù)依次查詢,輸出sql條件的所有數(shù)據(jù)

 public  func querySql(sql:String) -> [[String:Any]]? {
        objc_sync_enter(self)
        if  !self.openDB() {
            objc_sync_exit(self)
            return nil
        }
        var arr:[[String:Any]] = []
        var  statement: OpaquePointer? = nil
        if sqlite3_prepare_v2(db,sql.cString(using: String.Encoding.utf8)!,-1,&statement,nil) == SQLITE_OK {
            while sqlite3_step(statement) == SQLITE_ROW {
                let columns = sqlite3_column_count(statement)
                var row:[String:Any] = Dictionary()
                for i in 0..<columns {
                    let type = sqlite3_column_type(statement, i)
                    let chars = UnsafePointer<CChar>(sqlite3_column_name(statement, i))
                    let name =  String.init(cString: chars!, encoding: String.Encoding.utf8)
                    
                    var value: Any
                    switch type {
                    case SQLITE_INTEGER:
                        value = sqlite3_column_int(statement, i)
                    case SQLITE_FLOAT:
                        value = sqlite3_column_double(statement, i)
                    case SQLITE_TEXT:
                        let chars = UnsafePointer<CUnsignedChar>(sqlite3_column_text(statement, i))
                        value = String.init(cString: chars!)
                        
                    case SQLITE_BLOB:
                        let data = sqlite3_column_blob(statement, i)
                        let size = sqlite3_column_bytes(statement, i)
                        value = NSData(bytes:data, length:Int(size))
                    default:
                        value = ""
                        ()
                    }
                    
                    row.updateValue(value, forKey: "\(name!)")
                }
                arr.append(row)
            }
        }
        sqlite3_finalize(statement)

        objc_sync_exit(self)
        if arr.count == 0 {
            return nil
        }else{
            return arr
        }
        
    }

引入事務(wù),加快數(shù)據(jù)庫寫入

事務(wù)(Transaction)是一個對數(shù)據(jù)庫執(zhí)行工作單元。事務(wù)(Transaction)是以邏輯順序完成的工作單位或序列,可以是由用戶手動操作完成,也可以是由某種數(shù)據(jù)庫程序自動完成。
事務(wù)(Transaction)是指一個或多個更改數(shù)據(jù)庫的擴展。例如,如果您正在創(chuàng)建一個記錄或者更新一個記錄或者從表中刪除一個記錄,那么您正在該表上執(zhí)行事務(wù)。重要的是要控制事務(wù)以確保數(shù)據(jù)的完整性和處理數(shù)據(jù)庫錯誤。
實際上,您可以把許多的 SQLite 查詢聯(lián)合成一組,把所有這些放在一起作為事務(wù)的一部分進行執(zhí)行

BEGIN TRANSACTION 開啟一個事務(wù)

COMMIT TRANSACTION 提交事務(wù)是否成功

ROLLBACK TRANSACTION 回滾事務(wù),當(dāng)數(shù)據(jù)庫事務(wù)操作失敗后,還原之前的操作。

注意:事務(wù)并不能批量優(yōu)化查詢速度。

   public func doTransaction(exec: ((_ db:OpaquePointer)->())?) {
        objc_sync_enter(self)
        if  !self.openDB() {
            objc_sync_exit(self)
            return
        }
        if exec != nil {
             var err: UnsafeMutablePointer<Int8>? = nil
            if sqlite3_exec(db, "BEGIN TRANSACTION", nil, nil, &err) == SQLITE_OK {
                exec!(db!)
                if sqlite3_exec(db, "COMMIT TRANSACTION", nil, nil, &err) == SQLITE_OK {
                    print("提交事務(wù)成功")
                }else {
                    print("提交事務(wù)失敗原因\(err)")
                    if let error = String(validatingUTF8:sqlite3_errmsg(db)) {
                        print("execute failed to execute  Error: \(error)")
                    }
                    if sqlite3_exec(db, "ROLLBACK TRANSACTION", nil, nil, &err) == SQLITE_OK {
                        print("回滾事務(wù)成功")
                    }
                }
            }else {
                if sqlite3_exec(db, "ROLLBACK TRANSACTION", nil, nil, &err) == SQLITE_OK {
                    print("回滾事務(wù)成功")
                }
            }
           sqlite3_free(err)
           
        }

        objc_sync_exit(self)
    }

SQLite3支持有限的 ALTER TABLE 操作

SQLite 有有限地 ALTER TABLE 支持。你可以使用它來在表的末尾增加一列,可更改表的名稱。 如果需要對表結(jié)構(gòu)做更復(fù)雜的改變,則必須重新建表。重建時可以先將已存在的數(shù)據(jù)放到一個臨時表中,刪除原表, 創(chuàng)建新表,然后將數(shù)據(jù)從臨時表中復(fù)制回來。在增加表列時,需注意:因為app在市場上存在許多版本,各個版本的數(shù)據(jù)庫表的結(jié)構(gòu)可能存在梯度的差異,代碼中使用就需要加入版本控制了。例如代碼中添加一個‘id’字段。

        let defaults = UserDefaults.standard
        let version = defaults.value(forKey: USER_SQL_VERSION)//控制刪除數(shù)據(jù)庫的版本記錄
        let update = defaults.value(forKey: USER_SQL_UPDATE)//控制增加數(shù)據(jù)庫字段的版本記錄
        if let version = version ,(version as! String) == USER_SQL_VERSION_CODE {
            if let update = update as? String{
                if  let intUpdate = Int(update.replacingOccurrences(of: ".", with: "")) {
                    if intUpdate > 100 {
                        
                        if let _ = SQLiteTable.shared.querySql(sql: "select id from \(USER_TABLENAME)"){
                            
                        } else {
                             SQLiteTable.shared.execSql(sql: "ALTER TABLE \(USER_TABLENAME)  ADD COLUMN  \("id") INTEGER DEFAULT 0 IF NOT EXISTS")
                
                        }
                        
                        
                    }
                }
               
            }
            
            defaults.setValue(USER_SQL_UPDATE_CODE, forKey: USER_SQL_UPDATE)
        }else {
            SQLiteTable.shared.dropTable(tableName: USER_TABLENAME)
            defaults.setValue(USER_SQL_VERSION_CODE, forKey: USER_SQL_VERSION)
        }

SQLite3中添加索引

    /// 創(chuàng)建一張表
    ///
    /// - Parameters:
    ///   - tableName: 表名
    ///   - data: 數(shù)據(jù)字段
    ///   - dataArray: 添加索引的字段
    /// - Returns: 是否成功   
public func createTable(tableName:String, andColoumName data:[String:String] ,andAddIndex dataArray:[String]) -> Bool {
        let result = self.createTable(tableName: tableName, andColoumName: data)
        dataArray.forEach { (str) in
            self.execSql(sql: "CREATE INDEX IF NOT EXISTS index_\(str)  ON \(tableName) (\(str))")
        }
        
         return result
        
    }
    

處理Dada類型數(shù)據(jù)

sqlite3__exec函數(shù)并不是萬能的,它就無法處理二進制數(shù)據(jù)。處理二進制數(shù)據(jù),需要的是另一種方法。
先對表的數(shù)據(jù)解析然后綁定到結(jié)構(gòu)體中,然后對數(shù)據(jù)庫進行INSERT DELETE 或者 UPDATE操作。

 private func bindSqlType(sql:String, params:[Any]?) -> OpaquePointer? {
        
        if  !self.openDB() {
            return nil
        }
        
        var stmt:OpaquePointer? = nil
        let someCharChar = unsafeBitCast(-1, to:sqlite3_destructor_type.self)
        let result = sqlite3_prepare_v2(db, sql.cString(using: String.Encoding.utf8)!, -1, &stmt, nil)
        if result != SQLITE_OK {
            sqlite3_finalize(stmt)
            if let error = String(validatingUTF8:sqlite3_errmsg(db)) {
                print("execute failed to execute  Error: \(error)")
            }
            return nil
        }
        
        if let  params = params {
            
            let count = CInt(params.count)
            if sqlite3_bind_parameter_count(stmt)  == count {
                var result:CInt = 0
                
                for index in 1...count {
                    
                    if let txt = params[index-1] as? String {
                        result = sqlite3_bind_text(stmt, CInt(index), txt, -1, someCharChar)
                    } else if let data = params[index-1] as? NSData {
                        result = sqlite3_bind_blob(stmt, CInt(index), data.bytes, CInt(data.length), someCharChar)
                    }else if let val = params[index-1] as? Double {
                        result = sqlite3_bind_double(stmt, CInt(index), CDouble(val))
                    } else if let val = params[index-1] as? Int {
                        result = sqlite3_bind_int64(stmt, index, Int64(val))
                    } else {
                        result = sqlite3_bind_null(stmt, CInt(index))
                    }
                    
                    if result != SQLITE_OK {
                        sqlite3_finalize(stmt)
                        if let error = String(validatingUTF8:sqlite3_errmsg(db)) {
                            print("execute failed to execute  Error: \(error)")
                        }
                        return nil
                    }
                }
            }
           
        }
        return stmt
    }

SQLite3的一些小坑

1.SQL 標(biāo)準(zhǔn)規(guī)定,在字符串中,單引號需要使用逃逸字符(''),即在一行中使用兩個單引號。

2.每次操作完數(shù)據(jù)庫記得關(guān)閉數(shù)據(jù)庫,防止多個數(shù)據(jù)庫混淆。

SQLite3中查詢效率優(yōu)化

1.添加索引 在demo中測試發(fā)現(xiàn),插入50000行數(shù)據(jù),同時做查詢時發(fā)現(xiàn),添加索引耗時76s,不添加花費256s。添加索引需注意:

  • 添加索引的原則是為了查詢更加快,但是一張表內(nèi)不能創(chuàng)建太多索引,因為索引只增加了相應(yīng)的 select 的效率,但同時也降低了 insert 及 update 的效率,一個表的索引數(shù)最好不要超過6個。

  • 在使用索引字段作為條件時,如果該索引是復(fù)合索引,那么必須使用到該索引中的第一個字段作為條件時才能保證系統(tǒng)使用該索引,否則該索引將不會被使 用,并且應(yīng)盡可能的讓字段順序與索引順序相一致。

2.在使用索引字段時,需要避免使用OR/BETWEEN/LIKE這些語法,這樣會避免使用索引而對表進行全局掃描。如demo中使用like語法與‘==’插入50000行數(shù)據(jù),同時做查詢時,對比發(fā)現(xiàn)分別耗時546s和76s。

SQLite3加密

使用SQLCipher鏈接地址這個第三方庫對數(shù)據(jù)庫進行加密。

sqlite3_key 加密函數(shù)

sqlite3_rekey 修改密碼

最后

點擊鏈接地址進入觀看完整代碼

具體請參考: https://github.com/tianjifou/CoreSQLite3.git;(轉(zhuǎn)載請說明出處,編寫代碼不易如對您有用請點贊,謝謝支持。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容