原文:SQLite Tutorial: Getting Started
注意:在2016-04-06已经更新到 Xcode 7.3
iOS 9.3
和 Swift 2.2
。
在软件开发的世界中,在你需要对你的 app
进行数据持久化前开发它并不用花费很多时间。在许多情况下,这是以数据结构的形式出现的,但你如何更有效地存储它?
幸运的是,一些伟大的先行者已经开发了关于访问存储在数据库中的结构化数据和写入语言的功能的解决方案。这个 SQLite
教程向您展示了如何快速使用 Swift
在流行平台工作上使用 SQLite 数据库,在 iOS
平台 SQLite
是默认可用的。如果你熟悉 Core Data
的话,你就会知道 SQLite
一些和 Core Data
类似的最常见持久化对象图的方法。
在本SQLite的教程中,您将学习到以下几种如何执行数据库的操作:
- 创建和连接一个数据库
- 创建一个表
- 插入行
- 更新行
- 删除行
- 查询数据库
- 处理
SQLite
错误
在学完这些基本的如何执行操作后,你会看到如何用一种更加 Swift
的方式将它们包装起来。它会帮助在你的应用中写更加抽象 APIs
,这样的话就可以大量的避免直接面对 SQLite
中的C语言的 APIs
!
最后,我将简单介绍流行的开源资源 Swift
封装的 SQLite.swift 的底层框架内是如何工作的。
Note: 数据库,甚至是SQLite自身,是巨大的主题,所以它们的很多内容超出了本教程的范围。本文假定你对于关系数据库的思想有基本的了解,你主要是来学习如何结合Swift来使用SQLite。
开始
下载这个 SQLite
教程的 启动项目 ,打开 SQLiteTutorial.xcworkspace
。根据项目的导航栏打开 Tutorial.playground
。你会马上注意到 import SQLite
那行有一个错误信息,你至少要编译项目一次,可以使用 Command + B
。这是因为需要工作空间的配置信息来创建一个模块,这样SQLite才可以运行在 playground
。
Note: SQLite是不正确的模块,你不能在你的项目中调用导入SQLite模块,不要被
import SQLite
这行误导;相反,你可以使用一个桥接的头部。
在 playground
打开的时候,设置它为手动运行,而不是自动运行:
这将有助于确保按你想那样运行SQL命令。
在这个页面的顶部你也可以看到一个 destroyPart1Database()
函数的调用;你可以安全地忽略这个,由于每次运行 playground
时,数据库文件会被破坏。这将确保你在这个SQLite教程所有语句成功执行。
你的 playground
的文件系统中,在需要的地方写SQLite数据库文件。可以在终端中运行下面的命令,为您的 playground
创建数据目录:
mkdir -p ~/Documents/Shared\ Playground\ Data/SQLiteTutorial
我为什么要选择SQLite?
对的,在iOS中 SQLite
不是唯一保存数据的途径。除了 Core Data ,还有很多数据持久化的选择,包括 Realm , Couchbase Lite , Firebase 和 NSCoding 。
每一个都有自己的优点和缺点,包括 SQLite
自身。在没有数据持久层,作为开发者,基于你的应用要求来决定选择哪个选项。
SQLite
确实有一定的优势:
- 在iOS的应用程序包没有增加额外的开销
- 尝试和测试;第一个版本在2000年8月发布
- 开源
- 对于数据库开发人员和管理员熟悉的查询语言
- 跨平台
SQLite
的优劣可以说非常直观和明显,所以这些我们会留下给你自己研究!
C的API
这部分的 SQLite
教程通过最常用和基本的 SQLite
的API来引导你。你会很快意识到用 Swift
的方法来包装C语言的API是个很好的想法,但是坐下来,我们先通过C语言去实现;你将在本教程的第二部分做一些 SQLite
的包装。
打开一个连接
在你做任何事情之前,你都需要先创建一个数据库的连接。
在开始部分的 playground
上添加如下方法:
func openDatabase() -> COpaquePointer {
var db: COpaquePointer = 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.")
XCPlaygroundPage.currentPage.finishExecution()
}
}
上面的方法调用 sqlite3_open()
来打开或创建一个新的数据库文件,如果成功,将返回一个 COpaquePointer
;它是一个 Swift
类型,以为C指针不能直接在Swift中表示。当你调用这个方法,您将会捕获到返回的指针,用于与数据库交互。
许多的SQLite函数返回一个 Int32
结果,大多数这些代码被定义为在SQLite库常数,例如, SQLITE_OK
代表结果代码0。你可以在SQLite的网站上找到一个关于不同结果代码的列表。
去打开数据库,在你的 playground
上添加以下行:
let db = openDatabase()
按下 Play
按钮运行playground,观察控制台输出。如果控制台没有打开,按下左边的按钮来运行:
如果 openDatabase()
返回成功,你会看到以下输出内容:
Successfully opened connection to database at /Users/username/
Documents/Shared Playground Data/SQLiteTutorial/Part1.sqlite
username
是你的 Home
目录。
创建表
现在你已经连接上数据库文件了,你可以创建一张表。您将使用一个非常简单的表来存储联系人。
下面这张表由两个列组成, id
的的数据类型是 INT
是表的主键, Name
的数据类型是 CHAR(255)
.
添加下面的字符串,是创建表必要的SQL语句:
let createTableString = "CREATE TABLE Contact(" + "Id INT PRIMARY KEY NOT NULL," + "Name CHAR(255));"
下一步,添加此方法来执行创建表的SQL语句:
func createTable() {
// 1
var createTableStatement: COpaquePointer = 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)
}
一步一步来分解:
首先,你创建一个指针便于第二步的引用。
sqlite3_prepare_v2()
函数将SQLite
语句编译成字节码并返回一个状态码,在执行任意数据库语句前这是很重要的一步。如果你对这个感兴趣,你可以在这里按查看更多,通过检查返回的状态码来确保编译成功。如果是这样的话,这个过程将移动到步骤3;否则,控制台会打印一条语句无法编译的消息。sqlite3_step()
运行已编译的语句,在这种情况下,只有当有一个结果时,才执行一次。之后在这个SQLite
的教程它是必要的步骤,你会多次看到它的声明。你必须总是调用
sqlite3_finalize()
编译语句删除它,避免资源泄漏。一旦一个语句已经完成,你就不应该再使用它了。
现在,你可以添加下面的方法在 playground
中调用:
createTable()
运行 playground
,你会在控制台上看下面的信息出现:
Contact table created
现在你的数据库里有一张表了,是时间往里面添加些数据了。添加一行数据, id
是 1
和 Name
是 Ray
。
插入一些数据
添加下面的SQL语句到你的 playground
的底部:
let insertStatementString = "INSERT INFO Contact (Id, Name) VALUES (?, ?);"
如果你没有太多开发SQL的经验也许看起来有一点奇怪。为什么值是问号呢?
还记得上面使用的 sqlite3_prepare_v2()
编译的句子?这个 ?
的语法是告诉编译器,当实际执行语句时,将提供真正的值。
这是有性能上的考虑,让你提前编译语句,这可能是一个性能增益,因为编译是一个昂贵的操作。编译后的语句可以重新使用不同的值。
接下来,创建下面的方法在你的 playground
:
func insert() {
var insertStatement: COpaquePointer = 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 staremnet could not be prepared.")
}
// 5
sqlite3_finalize(insertStatement)
}
下面介绍上面的方法是如何工作的:
- 首先,编写SQL语句并验证是否正确;
- 在
?
占位符得地方定义值。函数的名字sqlite3_bind_int()
- 意味着你绑定一个int
值的声明。函数的第一个参数是绑定到的语句,而第二个参数是一个非零的索引用于绑定?
的位置。第三个和最后一个参数是值本身。这个绑定调用返回一个状态代码,但现在你假设它成功了的; - 执行相同的绑定过程,但是这一次是一个文本值。在这个调用里有两个额外的参数;在这个SQLite的教程里你可以简单设为
-1
和nil
。如果你有兴趣,你可以在这里阅读更多关于绑定参数; - 使用
sqlite3_step()
函数执行的语句和验证完成; - 一如既往,最后确定声明。如果您要插入多个联系人,您可能会保留该语句,并使用不同的值重新使用它。
下一步,在 playground
中调用下面这个新添加的方法:
insert()
运行你的 playground
和验证您看到您在控制台输出:
Successfully inserted row.
挑战:多条插入
挑战时间!你的任务是更新 insert()
函数使它可以插入一组联系人。
作为一个提示,你需要重新编写 SQL
语句,用 sqlite3_reset()
返回到它的初始状态再执行一遍。
解决方案:插入多行
func insert() {
var insertStatement: COpauePointer = nil
// 1
let names: [NSString] = ["Ray", "Chirs", "Martha", "Danielle"]
if sqlite3_prepare_v2(db, insertStatementString, -1, &insertStatement, nil) == SQLITE_OK {
// 2
for (index, name) in names.enumerate() {
// 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.")
}
}