數(shù)據(jù)持久化方案解析(一) —— 一個(gè)簡(jiǎn)單的基于SQLite持久化方案示例(一)

版本記錄

版本號(hào) 時(shí)間
V1.0 2018.10.12 星期五

前言

數(shù)據(jù)的持久化存儲(chǔ)是移動(dòng)端不可避免的一個(gè)問(wèn)題,很多時(shí)候的業(yè)務(wù)邏輯都需要我們進(jìn)行本地化存儲(chǔ)解決和完成,我們可以采用很多持久化存儲(chǔ)方案吟吝,比如說(shuō)plist文件(屬性列表)蚓胸、preference(偏好設(shè)置)、NSKeyedArchiver(歸檔)蓖捶、SQLite 3地回、CoreData,這里基本上我們都用過(guò)。這幾種方案各有優(yōu)缺點(diǎn)刻像,其中畅买,CoreData是蘋果極力推薦我們使用的一種方式,我已經(jīng)將它分離出去一個(gè)專題進(jìn)行說(shuō)明講解细睡。這個(gè)專題主要就是針對(duì)另外幾種數(shù)據(jù)持久化存儲(chǔ)方案而設(shè)立谷羞。

開(kāi)始

首先看一下寫作環(huán)境

Swift 4, iOS 11, Xcode 9

本文將學(xué)習(xí)如何在Swift項(xiàng)目中使用SQLite數(shù)據(jù)庫(kù),包括插入溜徙,更新和刪除行湃缎。

這個(gè)帶有Swift的SQLite文章向您展示了如何在Swift中使用流行的數(shù)據(jù)庫(kù)平臺(tái)。 在軟件開(kāi)發(fā)領(lǐng)域萌京,您需要很長(zhǎng)時(shí)間才能保留應(yīng)用數(shù)據(jù)雁歌。 在許多情況下,這是以數(shù)據(jù)結(jié)構(gòu)的形式出現(xiàn)的知残。 但是靠瞎,如何有效地存儲(chǔ)它?

幸運(yùn)的是求妹,一些偉大的思想家已經(jīng)開(kāi)發(fā)出用于在數(shù)據(jù)庫(kù)中存儲(chǔ)結(jié)構(gòu)化數(shù)據(jù)和編寫語(yǔ)言功能以訪問(wèn)數(shù)據(jù)的解決方案乏盐。 SQLite默認(rèn)在iOS上可用。 實(shí)際上制恍,如果您以前使用過(guò)Core Data父能,那么您實(shí)際上已經(jīng)使用過(guò)SQLite,因?yàn)?code>Core Data只是SQLite上面的一個(gè)層净神,它提供了更方便的API何吝。

在整個(gè)文章中,您將學(xué)習(xí)如何執(zhí)行以下數(shù)據(jù)庫(kù)操作:

  • 創(chuàng)建并連接到數(shù)據(jù)庫(kù)
  • 創(chuàng)建一個(gè)表
  • 插入一行
  • 更新一行
  • 刪除一行
  • 查詢數(shù)據(jù)庫(kù)
  • 處理SQLite錯(cuò)誤

在學(xué)習(xí)如何執(zhí)行這些基本操作之后鹃唯,您將看到如何以類似Swift的方式將它們包裝起來(lái)爱榕。 這將允許您為應(yīng)用程序編寫抽象API,以便您(大多數(shù))可以避免使用SQLite C API的痛苦坡慌!

最后黔酥,我將簡(jiǎn)要介紹流行的開(kāi)源Swift包裝器SQLite.swift,以便您基本了解底層框架如何在包裝器中工作洪橘。

注意:數(shù)據(jù)庫(kù)跪者,甚至只是SQLite本身,都是要涵蓋的大量主題熄求,因此它們大多超出了本教程的范圍渣玲。 假設(shè)您對(duì)關(guān)系數(shù)據(jù)庫(kù)意識(shí)形態(tài)有基本的了解,并且您主要在這里學(xué)習(xí)如何結(jié)合使用SQLite和Swift抡四。

打開(kāi)已經(jīng)編制好的SQLite的入門項(xiàng)目并打開(kāi)SQLiteTutorial.xcworkspace柜蜈。 從Project Navigator中打開(kāi)Tutorial playground仗谆。

注意:項(xiàng)目打包在Xcode工作區(qū)中指巡,因?yàn)樗褂肧QLite3依賴項(xiàng)作為嵌入式二進(jìn)制文件淑履。 此二進(jìn)制文件包含您將在本教程中編寫的SQLite代碼的所有功能。

請(qǐng)注意藻雪,您的Playground配置為手動(dòng)而不是自動(dòng)運(yùn)行:

這意味著它只會(huì)在您通過(guò)點(diǎn)擊Play按鈕顯式調(diào)用執(zhí)行時(shí)執(zhí)行秘噪。

您可能還會(huì)在頁(yè)面頂部看到destroyPart1Database()調(diào)用;您可以放心地忽略這一點(diǎn)勉耀,因?yàn)槊看?code>playground運(yùn)行時(shí)都會(huì)銷毀數(shù)據(jù)庫(kù)文件指煎。 這可確保在使用Swift教程瀏覽此SQLite時(shí),所有語(yǔ)句都能成功執(zhí)行便斥。

你的playground需要在你的文件系統(tǒng)上編寫SQLite數(shù)據(jù)庫(kù)文件至壤。 在終端中運(yùn)行以下命令以創(chuàng)建playground的數(shù)據(jù)目錄:

mkdir -p ~/Documents/Shared\ Playground\ Data/SQLiteTutorial

Why Should I Choose SQLite? - 我為什么要選擇SQLite?

沒(méi)錯(cuò)枢纠,SQLite不是在iOS上持久保存數(shù)據(jù)的唯一方法像街。 除了Core Data之外,還有許多其他的數(shù)據(jù)持久性替代方案晋渺,包括Realm镰绎,Couchbase LiteFirebaseNSCoding木西。

每個(gè)都有自己的優(yōu)點(diǎn)和缺點(diǎn) - 包括SQLite本身畴栖。 數(shù)據(jù)持久性沒(méi)有靈丹妙藥,作為開(kāi)發(fā)人員八千,您可以根據(jù)應(yīng)用程序的要求確定哪個(gè)選項(xiàng)超過(guò)其他選項(xiàng)吗讶。

SQLite確實(shí)有一些優(yōu)點(diǎn):

  • 隨iOS一起提供,因此它不會(huì)為您的應(yīng)用程序包增加任何開(kāi)銷
  • 試過(guò)并經(jīng)過(guò)測(cè)試恋捆;1.0版于2000年8月發(fā)布
  • 開(kāi)源
  • 適用于數(shù)據(jù)庫(kù)開(kāi)發(fā)人員和管理員熟悉的查詢語(yǔ)言
  • 跨平臺(tái)

SQLite的缺點(diǎn)可能是非常主觀的照皆,就把研究留給你了!


The C API - C API

SQLite with Swift教程的這部分將引導(dǎo)您完成最常見(jiàn)和最基本的SQLite API鸠信。 你很快就會(huì)意識(shí)到在Swift方法中包裝C API是理想的纵寝,但要緊緊抓住并首先完成C代碼;你將在本教程的第二部分做一些包裝星立。

1. Opening a Connection - 打開(kāi)連接

在做任何事情之前爽茴,您首先需要?jiǎng)?chuàng)建一個(gè)數(shù)據(jù)庫(kù)連接。

playground開(kāi)始部分下添加以下方法:

func openDatabase() -> OpaquePointer? {
  var db: OpaquePointer? = nil
  if sqlite3_open(part1DbPath, &db) == SQLITE_OK {
    print("Successfully opened connection to database at \(part1DbPath)")
    return db
  } else {
    print("Unable to open database. Verify that you created the directory described " +
      "in the Getting Started section.")
    PlaygroundPage.current.finishExecution()
  }
  
}

上面的方法調(diào)用sqlite3_open()绰垂,它打開(kāi)或創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)文件室奏。 如果成功,則返回OpaquePointer劲装;這是一個(gè)用于C指針的Swift類型胧沫,無(wú)法直接在Swift中表示昌简。 調(diào)用此方法時(shí),您必須捕獲返回的指針才能與數(shù)據(jù)庫(kù)進(jìn)行交互绒怨。

許多SQLite函數(shù)返回Int32結(jié)果代碼纯赎。 這些代碼中的大多數(shù)都被定義為SQLite庫(kù)中的常量。 例如南蹂,SQLITE_OK表示結(jié)果代碼0犬金。可以在on the main SQLite site上找到不同結(jié)果代碼的列表六剥。

要打開(kāi)數(shù)據(jù)庫(kù)晚顷,請(qǐng)將以下行添加到您的playground

let db = openDatabase()

Play按鈕運(yùn)行playground并觀看控制臺(tái)輸出。 如果控制臺(tái)未打開(kāi)疗疟,請(qǐng)按play按鈕左側(cè)的按鈕:

如果openDatabase()成功该默,您將看到如下輸出:

Successfully opened connection to database at /Users/username/Documents/Shared Playground Data/SQLiteTutorial/Part1.sqlite

其中username是您的Home目錄。

2. Creating a Table - 創(chuàng)建表

現(xiàn)在您已連接到數(shù)據(jù)庫(kù)文件策彤,您可以創(chuàng)建一個(gè)表栓袖。 您將使用一個(gè)非常簡(jiǎn)單的表來(lái)存儲(chǔ)聯(lián)系人。

該表將包含兩列; Id锅锨,是INTPRIMARY KEY叽赊;和Name,這是一個(gè)CHAR(255)必搞。

添加以下字符串必指,其中包含創(chuàng)建表所需的SQL語(yǔ)句:

let createTableString = """
CREATE TABLE Contact(
Id INT PRIMARY KEY NOT NULL,
Name CHAR(255));
"""

請(qǐng)注意,您正在使用Swift 4的便捷多語(yǔ)法來(lái)編寫此語(yǔ)句恕洲!

接下來(lái)塔橡,添加執(zhí)行CREATE TABLE SQL語(yǔ)句的此方法:

func createTable() {
  // 1
  var createTableStatement: OpaquePointer? = nil
  // 2
  if sqlite3_prepare_v2(db, createTableString, -1, &createTableStatement, nil) == SQLITE_OK {
    // 3
    if sqlite3_step(createTableStatement) == SQLITE_DONE {
      print("Contact table created.")
    } else {
      print("Contact table could not be created.")
    }
  } else {
    print("CREATE TABLE statement could not be prepared.")
  }
  // 4
  sqlite3_finalize(createTableStatement)
}

逐步完成這一步:

  • 1) 首先,在下一步中創(chuàng)建一個(gè)指向引用的指針霜第。
  • 2) sqlite3_prepare_v2()將SQL語(yǔ)句編譯為字節(jié)代碼并返回狀態(tài)代碼 - 在對(duì)數(shù)據(jù)庫(kù)執(zhí)行任意語(yǔ)句之前的重要步驟葛家。 如果您有興趣,可以在這里找到更多信息泌类。 檢查返回的狀態(tài)代碼以確保語(yǔ)句編譯成功癞谒。 如果是,則該過(guò)程轉(zhuǎn)到步驟3刃榨;否則弹砚,您打印一條消息,指出該語(yǔ)句無(wú)法編譯枢希。
  • 3) sqlite3_step()運(yùn)行已編譯的語(yǔ)句桌吃。 在這種情況下,您只需“步進(jìn)”一次苞轿,因?yàn)榇苏Z(yǔ)句只有一個(gè)結(jié)果茅诱。 稍后在這個(gè)帶有Swift教程的SQLite中逗物,您將看到何時(shí)需要多次執(zhí)行單個(gè)語(yǔ)句。
  • 4) 您必須始終在編譯語(yǔ)句上調(diào)用sqlite3_finalize()以刪除它并避免資源泄漏瑟俭。 一旦聲明完成宣蠕,您就不應(yīng)該再次使用它儒陨。

現(xiàn)在鸠匀,將以下方法調(diào)用添加到playground

createTable()

Run你的playground蝴蜓,您應(yīng)該看到控制臺(tái)輸出中出現(xiàn)以下內(nèi)容:

Contact table created.

現(xiàn)在您有了一個(gè)表拾氓,是時(shí)候向它添加一些數(shù)據(jù)了章鲤。 您將添加Id1且名稱為Ray的單行晴裹。

3. Inserting Some Data - 插入一些數(shù)據(jù)

將以下SQL語(yǔ)句添加到playground的底部:

let insertStatementString = "INSERT INTO Contact (Id, Name) VALUES (?, ?);"

如果您沒(méi)有太多的SQL經(jīng)驗(yàn)瘾敢,這可能看起來(lái)有點(diǎn)奇怪田盈。 為什么值由問(wèn)號(hào)代表畜号?

在使用sqlite3_prepare_v2()編譯語(yǔ)句時(shí),請(qǐng)記住上面的內(nèi)容允瞧。简软?語(yǔ)法告訴編譯器在實(shí)際執(zhí)行語(yǔ)句時(shí)將提供實(shí)際值。

這有性能方面的考慮述暂,并且允許您提前編譯語(yǔ)句痹升,這可以提高性能,因?yàn)榫幾g是一項(xiàng)代價(jià)高昂的操作畦韭。 然后可以使用不同的值反復(fù)重復(fù)使用已編譯的語(yǔ)句疼蛾。

接下來(lái),在您的playground中創(chuàng)建以下方法:

func insert() {
  var insertStatement: OpaquePointer? = nil

  // 1
  if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
    let id: Int32 = 1
    let name: NSString = "Ray"

    // 2
    sqlite3_bind_int(insertStatement, 1, id)
    // 3
    sqlite3_bind_text(insertStatement, 2, name.utf8String, -1, nil)

    // 4
    if sqlite3_step(insertStatement) == SQLITE_DONE {
      print("Successfully inserted row.")
    } else {
      print("Could not insert row.")
    }
  } else {
    print("INSERT statement could not be prepared.")
  }
  // 5
  sqlite3_finalize(insertStatement)
}

以下是上述方法的工作原理:

  • 1) 首先艺配,編譯語(yǔ)句并驗(yàn)證一切正常;
  • 2) 在這里察郁,您為占位符定義一個(gè)值转唉。函數(shù)的名稱--sqlite3_bind_int() - 意味著您將Int值綁定到語(yǔ)句皮钠。函數(shù)的第一個(gè)參數(shù)是要綁定的語(yǔ)句,而第二個(gè)參數(shù)是你要綁定的?非零的索引的位置赠法。第三個(gè)也是最后一個(gè)參數(shù)是值本身麦轰。此綁定調(diào)用返回狀態(tài)代碼,但現(xiàn)在您認(rèn)為它成功砖织。
  • 3) 執(zhí)行相同的綁定過(guò)程款侵,但這次是文本值。此次調(diào)用還有兩個(gè)附加參數(shù)镶苞;出于本教程的目的喳坠,您可以簡(jiǎn)單地為它們傳遞-1nil。如果您愿意茂蚓,可以在這里閱讀有關(guān)綁定參數(shù)的更多信息壕鹉。
  • 4) 使用sqlite3_step()函數(shù)執(zhí)行語(yǔ)句并驗(yàn)證它是否已完成剃幌。
  • 5) 一如既往,最終確定聲明晾浴。如果您要插入多個(gè)聯(lián)系人负乡,則可能會(huì)保留該語(yǔ)句并使用不同的值重新使用它。

接下來(lái)脊凰,通過(guò)將以下內(nèi)容添加到playground中來(lái)調(diào)用您的新方法:

insert()

運(yùn)行您的playground并驗(yàn)證您在控制臺(tái)輸出中看到以下內(nèi)容:

Successfully inserted row.

4. Challenge: Multiple Inserts - 挑戰(zhàn):多個(gè)插入

挑戰(zhàn)時(shí)間抖棘! 您的任務(wù)是更新insert()以插入聯(lián)系人數(shù)組。

作為提示狸涌,您需要在再次執(zhí)行之前調(diào)用sqlite3_reset()將已編譯的語(yǔ)句重置回其初始狀態(tài)切省。

func insert() {

  var insertStatement: OpaquePointer? = nil
  // 1
  let names: [NSString] = ["Ray", "Chris", "Martha", "Danielle"]

  if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {

    // 2
    for (index, name) in names.enumerated() {
      // 3
      let id = Int32(index + 1)
      sqlite3_bind_int(insertStatement, 1, id)
      sqlite3_bind_text(insertStatement, 2, name.utf8String, -1, nil)

      if sqlite3_step(insertStatement) == SQLITE_DONE {
        print("Successfully inserted row.")
      } else {
        print("Could not insert row.")
      }
      // 4
      sqlite3_reset(insertStatement)
    }

    sqlite3_finalize(insertStatement)
  } else {
    print("INSERT statement could not be prepared.")
  }
}

正如您所看到的,代碼與您已有的代碼非常相似帕胆,但具有以下顯著差異:

  • 1) 現(xiàn)在有一系列聯(lián)系人朝捆,而不是一個(gè)常數(shù);
  • 2) 對(duì)每個(gè)聯(lián)系人遍歷一次數(shù)組懒豹;
  • 3) 現(xiàn)在芙盘,索引是從枚舉的索引生成的,該索引對(duì)應(yīng)于數(shù)組中聯(lián)系人姓名的位置脸秽;
  • 4) SQL語(yǔ)句在每個(gè)遍歷結(jié)束時(shí)重置儒老,以便下一個(gè)可以使用它。

5. Querying Contacts - 查詢聯(lián)系人

既然你已經(jīng)插入了一兩行记餐,那么確定它們真的很好用驮樊!

將以下內(nèi)容添加到playground

let queryStatementString = "SELECT * FROM Contact;"

此查詢只是從聯(lián)系人表中檢索所有記錄。 使用*表示將返回所有列剥扣。

添加以下方法以執(zhí)行查詢:

func query() {
  var queryStatement: OpaquePointer? = nil
  // 1
  if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {
    // 2
    if sqlite3_step(queryStatement) == SQLITE_ROW {
      // 3
      let id = sqlite3_column_int(queryStatement, 0)

      // 4
      let queryResultCol1 = sqlite3_column_text(queryStatement, 1)
      let name = String(cString: queryResultCol1!)

      // 5
      print("Query Result:")
      print("\(id) | \(name)")

    } else {
      print("Query returned no results")
    }
  } else {
    print("SELECT statement could not be prepared")
  }

  // 6
  sqlite3_finalize(queryStatement)
}

下面分步詳細(xì)說(shuō)明:

  • 1) Prepare語(yǔ)句巩剖;
  • 2) 執(zhí)行該語(yǔ)句。 請(qǐng)注意钠怯,您現(xiàn)在正在檢查狀態(tài)代碼SQLITE_ROW佳魔,這意味著您在逐步執(zhí)行結(jié)果時(shí)檢索了一行;
  • 3) 是時(shí)候從返回的行中讀取值了晦炊。 根據(jù)您對(duì)表的結(jié)構(gòu)和查詢的了解鞠鲜,您可以逐列訪問(wèn)行的值。 第一列是Int断国,因此您使用sqlite3_column_int()并傳入語(yǔ)句和從零開(kāi)始的列索引贤姆。 您將返回的值分配給本地范圍的id常量;
  • 4) 接下來(lái)稳衬,從Name列中獲取文本值霞捡。 由于C API,這有點(diǎn)亂薄疚。 首先碧信,將值捕獲為queryResultCol1赊琳,以便在下一行將其轉(zhuǎn)換為正確的Swift字符串;
  • 5) 打印出結(jié)果砰碴;
  • 6) 最后Finalize語(yǔ)句躏筏。

現(xiàn)在,通過(guò)將以下內(nèi)容添加到playground的底部來(lái)調(diào)用您的新方法:

query()

Run你的playground呈枉,您將在控制臺(tái)中看到以下輸出:

Query Result:
1 | Ray

W00t趁尼! 看起來(lái)你的數(shù)據(jù)是進(jìn)入數(shù)據(jù)庫(kù)的!

6. Challenge: Printing Every Row - 挑戰(zhàn):打印每一行

您的任務(wù)是更新query()以打印出表中的每個(gè)聯(lián)系人猖辫。

func query() {
  var queryStatement: OpaquePointer? = nil
  if sqlite3_prepare_v2(db, queryStatementString, -1, &queryStatement, nil) == SQLITE_OK {

    while (sqlite3_step(queryStatement) == SQLITE_ROW) {
      let id = sqlite3_column_int(queryStatement, 0)
      let queryResultCol1 = sqlite3_column_text(queryStatement, 1)
      let name = String(cString: queryResultCol1!)
      print("Query Result:")
      print("\(id) | \(name)")
    }

  } else {
    print("SELECT statement could not be prepared")
  }
  sqlite3_finalize(queryStatement)
}

請(qǐng)注意酥泞,不是像前面那樣使用單個(gè)步驟來(lái)檢索第一行,而是這次使用while循環(huán)來(lái)執(zhí)行步驟住册,只要返回代碼是SQLITE_ROW就會(huì)發(fā)生婶博。 當(dāng)您到達(dá)最后一行時(shí),返回代碼將通過(guò)SQLITE_DONE荧飞,循環(huán)將中斷。

7. Updating Contacts - 更新聯(lián)系人

下一個(gè)自然進(jìn)展是更新現(xiàn)有行名党。 你應(yīng)該開(kāi)始看到一種模式出現(xiàn)了叹阔。

首先,創(chuàng)建UPDATE語(yǔ)句:

let updateStatementString = "UPDATE Contact SET Name = 'Chris' WHERE Id = 1;"

在這里你使用真正的值而不是传睹?占位符耳幢。 通常你會(huì)使用占位符并執(zhí)行適當(dāng)?shù)恼Z(yǔ)句綁定,但為了簡(jiǎn)潔起見(jiàn)欧啤,你可以在這里跳過(guò)它睛藻。

接下來(lái),將以下方法添加到playground

func update() {
  var updateStatement: OpaquePointer? = nil
  if sqlite3_prepare_v2(db, updateStatementString, -1, &updateStatement, nil) == SQLITE_OK {
    if sqlite3_step(updateStatement) == SQLITE_DONE {
      print("Successfully updated row.")
    } else {
      print("Could not update row.")
    }
  } else {
    print("UPDATE statement could not be prepared")
  }
  sqlite3_finalize(updateStatement)
}

這與您之前看到的類似:prepare, step, finalize邢隧!將以下內(nèi)容添加到您的playground

update()
query()

這將執(zhí)行您的新方法店印,然后調(diào)用您先前定義的query()方法,以便您可以看到結(jié)果:

Successfully updated row.
Query Result:
1 | Chris

恭喜您更新第一行倒慧! 這多么容易按摘。

8. Deleting Contacts - 刪除聯(lián)系人

下面看一下刪除您創(chuàng)建的行。 再次纫谅,您將使用熟悉的prepare, step, and finalize炫贤。

將以下內(nèi)容添加到playground

let deleteStatementStirng = "DELETE FROM Contact WHERE Id = 1;"

現(xiàn)在添加以下方法來(lái)執(zhí)行語(yǔ)句:

func delete() {
  var deleteStatement: OpaquePointer? = nil
  if sqlite3_prepare_v2(db, deleteStatementStirng, -1, &deleteStatement, nil) == SQLITE_OK {
    if sqlite3_step(deleteStatement) == SQLITE_DONE {
      print("Successfully deleted row.")
    } else {
      print("Could not delete row.")
    }
  } else {
    print("DELETE statement could not be prepared")
  }
  
  sqlite3_finalize(deleteStatement)
}

你現(xiàn)在掌握了嗎?Prepare, step, and finalize付秕!

執(zhí)行這個(gè)新方法兰珍,然后調(diào)用query(),如下所示:

delete()
query()

現(xiàn)在運(yùn)行你的playground询吴,你應(yīng)該在你的控制臺(tái)中看到以下輸出:

Successfully deleted row.
Query returned no results

注意:如果您完成了上面的“多個(gè)插入”挑戰(zhàn)掠河,由于表中仍存在行励幼,因此輸出可能與上面的內(nèi)容略有不同。

9. Handling Errors - 處理錯(cuò)誤

到目前為止口柳,希望你已經(jīng)設(shè)法避免SQLite錯(cuò)誤苹粟。 但是,當(dāng)你進(jìn)行沒(méi)有意義的調(diào)用跃闹,或者根本無(wú)法編譯時(shí)嵌削,錯(cuò)誤將會(huì)到來(lái)。

在發(fā)生這些事情時(shí)處理錯(cuò)誤消息可以節(jié)省大量的開(kāi)發(fā)時(shí)間望艺,它還使您有機(jī)會(huì)向用戶顯示有意義的錯(cuò)誤消息苛秕。

將以下語(yǔ)句 - 固定格式錯(cuò)誤 - 添加到您的playground

let malformedQueryString = "SELECT Stuff from Things WHERE Whatever;"

現(xiàn)在添加一個(gè)方法來(lái)執(zhí)行這個(gè)格式錯(cuò)誤的語(yǔ)句:

func prepareMalformedQuery() {
  var malformedStatement: OpaquePointer? = nil
  // 1
  if sqlite3_prepare_v2(db, malformedQueryString, -1, &malformedStatement, nil) == SQLITE_OK {
    print("This should not have happened.")
  } else {
    // 2
    let errorMessage = String.init(cString: sqlite3_errmsg(db))
    print("Query could not be prepared! \(errorMessage)")
  }
  
  // 3
  sqlite3_finalize(malformedStatement)
}

以下是您將如何強(qiáng)制執(zhí)行錯(cuò)誤:

  • 1) 準(zhǔn)備語(yǔ)句,該語(yǔ)句將失敗并且不應(yīng)返回SQLITE_OK找默;
  • 2) 使用sqlite3_errmsg()從數(shù)據(jù)庫(kù)中獲取錯(cuò)誤消息艇劫。 此函數(shù)返回最近錯(cuò)誤的文本描述。 然后惩激,您將錯(cuò)誤打印到控制臺(tái)店煞;
  • 3) 一如既往,最終finalize风钻。

調(diào)用該方法以查看錯(cuò)誤消息:

prepareMalformedQuery()

Run你的playground顷蟀,您應(yīng)該在控制臺(tái)中看到以下輸出:

Query could not be prepared! no such table: Things

嗯,這實(shí)際上很有幫助 - 你顯然無(wú)法在不存在的表上運(yùn)行SELECT語(yǔ)句骡技!

10. Closing the Database Connection - 關(guān)閉數(shù)據(jù)庫(kù)連接

完成數(shù)據(jù)庫(kù)連接后鸣个,您將負(fù)責(zé)關(guān)閉它。 但請(qǐng)注意 - 在成功關(guān)閉數(shù)據(jù)庫(kù)之前布朦,必須執(zhí)行許多操作囤萤,如SQLite documentation中所述。

調(diào)用close函數(shù)是趴,如下所示:

sqlite3_close(db)

Run你的playground涛舍;您應(yīng)該在playground的右側(cè)結(jié)果視圖中看到狀態(tài)代碼0;這表示SQLITE_OK右遭,這意味著您的關(guān)閉調(diào)用成功做盅。

您已經(jīng)成功創(chuàng)建了一個(gè)數(shù)據(jù)庫(kù),添加了一個(gè)表窘哈,向表中添加了行吹榴,查詢并更新了這些行,甚至刪除了一行 - 所有這些都使用了Swift的SQLite C API滚婉。 很好图筹!

在下一節(jié)中,您將利用所學(xué)內(nèi)容,并了解如何在Swift中包含其中一些調(diào)用远剩。

后記

本篇主要講述了一個(gè)簡(jiǎn)單的基于SQLite持久化方案示例扣溺,感興趣的給個(gè)贊或者關(guān)注~~~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瓜晤,隨后出現(xiàn)的幾起案子锥余,更是在濱河造成了極大的恐慌,老刑警劉巖痢掠,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件驱犹,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡足画,警方通過(guò)查閱死者的電腦和手機(jī)雄驹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)淹辞,“玉大人医舆,你說(shuō)我怎么就攤上這事∠笞海” “怎么了蔬将?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)攻冷。 經(jīng)常有香客問(wèn)我娃胆,道長(zhǎng),這世上最難降的妖魔是什么等曼? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮凿蒜,結(jié)果婚禮上禁谦,老公的妹妹穿的比我還像新娘。我一直安慰自己废封,他們只是感情好州泊,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著漂洋,像睡著了一般遥皂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上刽漂,一...
    開(kāi)封第一講書(shū)人閱讀 52,549評(píng)論 1 312
  • 那天演训,我揣著相機(jī)與錄音,去河邊找鬼贝咙。 笑死样悟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播窟她,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼陈症,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了震糖?” 一聲冷哼從身側(cè)響起录肯,我...
    開(kāi)封第一講書(shū)人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吊说,沒(méi)想到半個(gè)月后论咏,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疏叨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年潘靖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蚤蔓。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡卦溢,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出秀又,到底是詐尸還是另有隱情单寂,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布吐辙,位于F島的核電站宣决,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏昏苏。R本人自食惡果不足惜尊沸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贤惯。 院中可真熱鬧洼专,春花似錦、人聲如沸孵构。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)颈墅。三九已至蜡镶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恤筛,已是汗流浹背官还。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叹俏,地道東北人妻枕。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親屡谐。 傳聞我的和親對(duì)象是個(gè)殘疾皇子述么,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

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