前言
在我們的日常開發中,經常會遇到使用者斷網或者網路較慢的情況,這樣使用者在一進入頁面的時候會顯示空白的頁面,那麼如何避免沒網顯示空白頁面的尷尬呢?答案就是:先在網路好的時候快取一部分資料,這樣當下次網路情況不好的時候,至少使用者可以先看到之前快取的內容,已達到提高APP的使用者體驗。 SQLite就是我們實現本地資料快取的一種方案,SQLite有以下優點:iOS內嵌SQLite;經過時間的驗證;開源;跨平臺。 OK,廢話不多說,現在我們就開始進入SQLite的體驗之旅。當然在開始之前我們要做一點準備工作,畢竟我們不打沒有準備的仗。
準備工作
建立備用資料
- 匯入SQLite3:
import SQLite3
- 建立一個Goods的類用來表示資料庫儲存的資料型別
- 建立一個Goods型別的陣列
- 宣告一個dbPath和db的全域性變數,宣告一個獲取
libraryDirectory
路徑的函式(資料庫存放路徑如何選擇)
程式碼如下:
class Goods {
let name: String!
let weight: Int!
var price: Double!
init(name: String, weight: Int, price: Double) {
self.name = name
self.weight = weight
self.price = price
}
}
let goods = Goods(name: "computer", weight: 10, price: 2000.0)
var goodArr = [Goods]()
var dbPath = ""
var db: OpaquePointer?
func createData() {
for index in 0...4 {
let goods = Goods(name: "computer" + "\(index)", weight: index * 10, price: 20.0)
goodArr.append(goods)
}
}
func fetchLibraryPath() {
if let libraryPathString = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true).first {
let pathURL = URL(fileURLWithPath: libraryPathString).appendingPathComponent("goods.sqlite")
dbPath = pathURL.path
}
}
複製程式碼
建立並連線資料庫
func openDatabase() -> OpaquePointer? {
var db: OpaquePointer?
if sqlite3_open(dbPath, &db) == SQLITE_OK {
resultLabel.text = "成功開啟資料庫,路徑:\(dbPath)"
return db
} else {
resultLabel.text = "開啟資料庫失敗"
return nil
}
}
複製程式碼
通過上面的程式碼我們可以看到,首先宣告瞭一個OpaquePointer
型別的可選值db
,接下來呼叫了sqlite3_open()
方法,該方法的作用是:如果之前建立了資料庫那麼直接開啟,若沒建立會直接建立一個。如果該方法呼叫成功,他會返回一個OpaquePointer
的值賦值給你傳遞進去的db
。
SQLITE_OK
是一個定義在SQLite庫中的一個常量,它代表一個Int32的0。SQLite的大多數函式都會返回一個Int32的值,例如SQLITE_ROW (100)
、SQLITE_DONE (101)
等,詳細列表你可以檢視這裡。
現在你可以通過呼叫db = openDatabase()
來開啟或者建立一個資料庫了,正常情況下你會看見成功開啟資料庫,路徑:xxx/xxx.sqlite
的輸出。
現在,我們已經成功的建立了一個名字為goods.sqlite
的資料庫了,接下來我們要做的就是建立一個表了。
建立表
程式碼
func createTable() {
let createTableString = """
CREATE TABLE Computer(
Id INT PRIMARY KEY NOT NULL,
Name CHAR(255),
Weight Int,
Price Float);
"""
var createTableStatement: OpaquePointer?
// 第一步
if sqlite3_prepare_v2(db, createTableString, -1, &createTableStatement, nil) == SQLITE_OK {
// 第二步
if sqlite3_step(createTableStatement) == SQLITE_DONE {
resultLabel.text = "成功建立表"
} else {
resultLabel.text = "未成功建立表"
}
} else {
}
//第三步
sqlite3_finalize(createTableStatement)
}
複製程式碼
程式碼說明
首先解釋一下createTableString
:建立一個名字為Computer的表,Id為主鍵且不為空,Name不超過255個字元,Weight為Int型別,Price為Float型別。
然後建立了一個OpaquePointer?
型別的變數用於下面的函式:sqlite3_prepare_v2()
。
- 第一步:該函式會將
createTableString
編譯為位元組程式碼(byte code)並返回一個status code,這個函式執行成功則表明database已經準備好了執行任意的SQL statement(就是建立的SQL的字串),該函式執行成功後即會執行sqlite3_step()
。 - 第二步:
sqlite3_step()
用來執行編譯完成的statement handle(createTableStatement)並返回一個status code。 - 第三步:在你每一次的操作完成後你必須呼叫
sqlite3_finalize()
去刪除你的statement以避免resource leak。注意:一旦一個statement被finalized,你不應該再一次使用它。
插入一條資料
程式碼
func insertOneData() {
let insertRowString = "INSERT INTO Computer (Id, Name, Weight, Price) VALUES (?, ?, ?, ?);"
var insertStatement: OpaquePointer?
//第一步
if sqlite3_prepare_v2(db, insertRowString, -1, &insertStatement, nil) == SQLITE_OK {
let id: Int32 = 1
//第二步
sqlite3_bind_int(insertStatement, 1, id)
sqlite3_bind_text(insertStatement, 2, goods.name, -1, nil)
sqlite3_bind_int(insertStatement, 3, Int32(goods.weight))
sqlite3_bind_double(insertStatement, 4, goods.price)
//第三步
if sqlite3_step(insertStatement) == SQLITE_DONE {
resultLabel.text = "插入資料成功"
} else {
resultLabel.text = "插入資料失敗"
}
} else {
}
//第四步
sqlite3_finalize(insertStatement)
}
複製程式碼
程式碼說明
- insertRowString中的
?
和前面的欄位是對應的,它只是佔位符的意思,告訴編譯器當真正執行該語句的時候會插入相應的值。 - 第二步:
sqlite3_bind_int()
標識你繫結了一個Int型別的值,該函式的第一個引數是你的statement(即insertStatement),第二個引數是?
的位置在你的statement(注意該值是非零的),在此處也就是1,第三個引數為你想繫結的值。sqlite3_bind_text()
函式表示你繫結的是一個text(一般用於比較長的字串)型別值,該函式比sqlite3_bind_int()
多了額外的兩個引數,第四個引數的意思是text的位元組數,一般穿-1
,第五個引數是一個closure回撥,處理完string後呼叫。 - 第三步第四步同上
插入多條資料
程式碼
func insertMutipleData() {
let insertRowString = "INSERT INTO Computer (Id, Name, Weight, Price) VALUES (?, ?, ?, ?);"
var insertStatement: OpaquePointer?
//第一步
if sqlite3_prepare_v2(db, insertRowString, -1, &insertStatement, nil) == SQLITE_OK {
for (index, good) in goodArr.enumerated() {
let id: Int32 = Int32(index + 1)
//第二步
sqlite3_bind_int(insertStatement, 1, id)
sqlite3_bind_text(insertStatement, 2, good.name, -1, nil)
sqlite3_bind_int(insertStatement, 3, Int32(good.weight))
sqlite3_bind_double(insertStatement, 4, good.price)
//第三步
if sqlite3_step(insertStatement) == SQLITE_DONE {
resultLabel.text = "插入資料成功"
} else {
resultLabel.text = "插入資料失敗"
}
//第四步
sqlite3_reset(insertStatement)
}
} else {
}
//第五步
sqlite3_finalize(insertStatement)
}
複製程式碼
程式碼說明
- insertRowString同上。
- 第四步:呼叫
sqlite3_reset()
函式,以便下次迴圈再次執行insertStatement - 第一步、第二步、第三步、第五步同上。
更新資料
程式碼
func updateData() {
let updateString = "UPDATE Computer SET Name = 'changeComputer' WHERE Id = 2;"
var updateStatement: OpaquePointer?
//第一步
if sqlite3_prepare_v2(db, updateString, -1, &updateStatement, nil) == SQLITE_OK {
//第二步
if sqlite3_step(updateStatement) == SQLITE_DONE {
resultLabel.text = "更新成功"
} else {
}
}
//第三步
sqlite3_finalize(updateStatement)
}
複製程式碼
程式碼說明
- updateString:將
Id==2
的資料的Name
欄位改為changeComputer
。 sqlite3_prepare_v2()
:準備,sqlite3_step()
:執行更新statement,sqlite3_finalize()
:結束。
刪除資料
程式碼
func deleteData() {
let deleteString = "DELETE FROM Computer WHERE Id = 2;"
var deleteStatement: OpaquePointer?
//第一步
if sqlite3_prepare_v2(db, deleteString, -1, &deleteStatement, nil) == SQLITE_OK {
//第二步
if sqlite3_step(deleteStatement) == SQLITE_DONE {
resultLabel.text = "刪除成功"
}
} else {
}
//第三步
sqlite3_finalize(deleteStatement)
}
複製程式碼
程式碼說明
- deleteString:刪除表中
Id==2
的資料。 sqlite3_prepare_v2()
:準備,sqlite3_step()
:執行刪除statement,sqlite3_finalize()
:結束。
查詢一條資料
程式碼
func queryOneData() {
let queryString = "SELECT * FROM Computer WHERE Id == 2;"
var queryStatement: OpaquePointer?
//第一步
if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK {
//第二步
if sqlite3_step(queryStatement) == SQLITE_ROW {
//第三步
let id = sqlite3_column_int(queryStatement, 0)
let queryResultName = sqlite3_column_text(queryStatement, 1)
let name = String(cString: queryResultName!)
let weight = sqlite3_column_int(queryStatement, 2)
let price = sqlite3_column_double(queryStatement, 3)
resultLabel.text = "id: \(id), name: \(name), weight: \(weight), price: \(price)"
} else {
resultLabel.text = "error"
}
}
//第四步
sqlite3_finalize(queryStatement)
}
複製程式碼
程式碼說明
- queryString:在
Computer
表中查詢所有Id == 2
的資料。 - 第二步:注意此時要判斷的status code為
SQLITE_ROW
,如果該判斷為true則代表你查詢的資料存在在表裡。 - 第三步:
sqlite3_column_int()
函式是按照列數取資料,第一個引數是statement,第二個引數則是該欄位是第幾列(Id 為表裡的第一列,從0開始計算)。sqlite3_column_text()
要略微複雜一點,他需要轉換型別通過String(cString: queryResultName!)
。 - 第一步、第四步同上
查詢多條資料
程式碼
func queryAllData() {
let queryString = "SELECT * FROM Computer;"
var queryStatement: OpaquePointer?
//第一步
if sqlite3_prepare_v2(db, queryString, -1, &queryStatement, nil) == SQLITE_OK {
//第二步
while(sqlite3_step(queryStatement) == SQLITE_ROW) {
//第三步
let id = sqlite3_column_int(queryStatement, 0)
let queryResultName = sqlite3_column_text(queryStatement, 1)
let name = String(cString: queryResultName!)
let weight = sqlite3_column_int(queryStatement, 2)
let price = sqlite3_column_double(queryStatement, 3)
resultLabel.text = "id: \(id), name: \(name), weight: \(weight), price: \(price)"
}
}
//第四步
sqlite3_finalize(queryStatement)
}
複製程式碼
程式碼說明
- 第二步:此處為
while
迴圈,當查詢到最後一行時會返回SQLITE_DONE
狀態碼來結束。 - 第一步第三步第四步同上。
小結
通過上面我們可以總結出執行一個statement的大概流程:sqlite3_prepare_v2()
:準備,sqlite3_step()
:執行statement,sqlite3_finalize()
:結束。好了,到這裡SQLite3的增刪改查基本操作也就完事了。下一篇我們來了解一下SQLite的進階用法。Bye~