斯威夫特4、iOS 11、Xcode 9
本文将学习如何在Swift 项目中使用SQLite 数据库,包括插入、更新和删除行。
这篇带有Swift 的SQLite 文章向您展示了如何在Swift 中使用流行的数据库平台。在软件开发领域,您需要长期保留应用程序数据。在许多情况下,这以数据结构的形式出现。但如何高效存储呢?
幸运的是,一些伟大的思想家已经开发出了在数据库中存储结构化数据并编写语言函数来访问数据的解决方案。 SQLite 默认在iOS 上可用。事实上,如果你以前使用过Core Data,那么你实际上也使用过SQLite,因为Core Data只是SQLite之上的一层,提供了更方便的API。
在本文中,您将学习如何执行以下数据库操作:
创建并连接到数据库创建表插入行更新行删除行查询数据库处理SQLite 错误在学习如何执行这些基本操作之后,您将了解如何以类似Swift 的方式包装它们。这将允许您为您的应用程序编写一个抽象API,这样您就可以(大部分)避免使用SQLite C API 的痛苦!
最后,我将简要介绍一下流行的开源Swift包装器SQLite.swift,以便您对包装器中的底层框架如何工作有一个基本的了解。
注意:数据库,甚至只是SQLite 本身,都是大量需要涵盖的主题,因此它们大多超出了本教程的范围。假设您对关系数据库思想有基本的了解,并且您主要是为了学习如何一起使用SQLite 和Swift。
打开已经编写好的SQLite 入门项目并打开SQLiteTutorial.xcworkspace。从项目导航器中打开教程游乐场。
注意:该项目打包在Xcode 工作区中,因为它使用SQLite3 依赖项作为嵌入式二进制文件。该二进制文件包含您将在本教程中编写的SQLite 代码的所有功能。
请注意,您的游乐场配置为手动运行而不是自动运行:
这意味着只有当您通过单击“播放”按钮显式调用执行时,它才会执行。
您还可能在页面顶部看到destroyPart1Database() 调用;您可以放心地忽略这一点,因为每次运行Playground 时数据库文件都会被销毁。这可确保在使用Swift 浏览本SQLite 教程时所有语句都能成功执行。
你的Playground 需要在你的文件系统上写入一个SQLite 数据库文件。在终端中运行以下命令来创建Playground 的数据目录:
mkdir -p ~/Documents/Shared Playground Data/SQLiteTutorial
Why Should I Choose SQLite? - 我为什么要选择SQLite?
是的,SQLite 并不是在iOS 上保存数据的唯一方法。除了Core Data 之外,还有许多其他数据持久性替代方案,包括Realm、Couchbase Lite、Firebase 和NSCoding。
每个都有自己的优点和缺点——包括SQLite 本身。数据持久性没有灵丹妙药,作为开发人员,您可以根据应用程序的要求来确定哪个选项胜过其他选项。
SQLite 确实有一些优点:
随iOS 一起提供,因此不会给您的应用程序包增加任何开销经过尝试和测试; 2000 年8 月发布1.0 版开源数据库开发人员和管理员熟悉的查询语言跨平台SQLite 缺点可能非常主观,我将把研究留给你!
The C API - C API
SQLite with Swift 教程的这一部分将引导您了解最常见和基本的SQLite API。您很快就会意识到,将C API 封装在Swift 方法中是理想的选择,但请先坚持并完成C 代码;您将在本教程的第二部分中进行一些包装。
1. Opening a Connection - 打开连接
在执行任何操作之前,您首先需要创建数据库连接。
在Playground 的开始部分下添加以下方法:
func openDatabase() -OpaquePointer? {
var db: 不透明指针?=零
如果sqlite3_open(part1DbPath, db)==SQLITE_OK {
print("已成功打开与位于(part1DbPath) 的数据库的连接")
返回数据库
} 别的{
print("无法打开数据库。验证您是否创建了描述的目录" +
“在入门部分。”)
PlaygroundPage.current.finishExecution()
}
}上面的方法调用sqlite3_open(),它打开或创建一个新的数据库文件。如果成功,返回一个OpaquePointer;这是C 指针的Swift 类型,不能直接在Swift 中表示。调用该方法时,必须捕获返回的指针才能与数据库交互。
许多SQLite 函数返回Int32 结果代码。大部分代码在SQLite 库中定义为常量。例如,SQLITE_OK 表示结果代码0。可以在SQLite 主站点上找到不同结果代码的列表。
要打开数据库,请将以下行添加到您的Playground 中:
let db=openDatabase() 按Play 按钮运行Playground 并观看控制台输出。如果控制台未打开,请按播放按钮左侧的按钮:
如果openDatabase() 成功,您将看到如下输出:
已成功打开与/Users/username/Documents/Shared Playground Data/SQLiteTutorial/Part1.sqlite 数据库的连接,其中username 是您的主目录。
2. Creating a Table - 创建表
现在您已连接到数据库文件,可以创建表了。您将使用一个非常简单的表来存储联系人。
该表将包含两列; Id,它是一个INT 和PRIMARY KEY;和名称,它是一个CHAR (255)。
添加以下字符串,其中包含创建表所需的SQL 语句:
让createTableString="""
创建表联系人(
Id INT 主键不为空,
名称CHAR(255));
"""请注意,您正在使用Swift 4 的便捷多语法来编写此语句!
接下来,添加执行CREATE TABLESQL 语句的方法:
函数创建表(){
//1
var createTableStatement: 不透明指针?=零
//2
如果sqlite3_prepare_v2(db, createTableString, -1, createTableStatement, nil)==SQLITE_OK {
//3
如果sqlite3_step(createTableStatement)==SQLITE_DONE {
print("联系人表已创建。")
} 别的{
print("无法创建联系人表。")
}
} 别的{
print("无法准备CREATE TABLE 语句。")
}
//4
sqlite3_finalize(createTableStatement)
逐步完成此操作:
1)首先,创建一个指向下一步引用的指针。 2) sqlite3_prepare_v2() 将SQL 语句编译为字节代码并返回状态代码- 这是对数据库执行任意语句之前的重要步骤。如果您有兴趣,可以在这里找到更多信息。检查返回的状态码以确保语句编译成功。如果是,则流程转至步骤3;如果是,则进入步骤3。否则,您将打印一条消息,指出该语句无法编译。 3) sqlite3_step()运行编译后的语句。在这种情况下,您只需“一步”一次,因为该语句只有一个结果。稍后在本SQLite 与Swift 教程中,您将看到何时需要多次执行单个语句。 4)您必须始终在编译语句上调用sqlite3_finalize() 以将其删除并避免资源泄漏。声明完成后,您不应再次使用它。现在,将以下方法调用添加到Playground 中:
createTable()运行你的playground,你应该会看到控制台输出中出现以下内容:
联系人表已创建。现在您已经有了一个表,是时候向其中添加一些数据了。您将添加ID 为1、名称为Ray 的单行。
3. Inserting Some Data - 插入一些数据
将以下SQL 语句添加到Playground 底部:
let insertStatementString="INSERT INTO Contact (Id, Name) VALUES (?);"如果您没有太多SQL 经验,这可能看起来有点奇怪。为什么数值用问号表示?
使用sqlite3_prepare_v2() 编译语句时请记住上述内容。语法告诉编译器,实际执行语句时将提供实际值。
这具有性能方面的考虑,并允许您提前编译语句,这可以提高性能,因为编译是一项昂贵的操作。然后可以使用不同的值反复重用已编译的语句。
接下来,在您的Playground 中创建以下方法:
函数插入(){
var insertStatement: 不透明指针?=零
//1
如果sqlite3_prepare_v2(db, insertStatementString, -1, insertStatement, nil)==SQLITE_OK {
让id: Int32=1
让name: NSString="雷"
//2
sqlite3_bind_int(insertStatement, 1, id)
//3
sqlite3_bind_text(insertStatement, 2, name.utf8String, -1, nil)
//4
如果sqlite3_step(insertStatement)==SQLITE_DONE {
print("成功插入行。")
} 别的{
print("无法插入行。")
}
} 别的{
print("无法准备INSERT 语句。")
}
//5
sqlite3_finalize(插入语句)
}以上方法的工作原理如下:
1)首先,编译语句并验证一切正常; 2) 在这里,你做什么?占位符定义一个值。函数的名称--sqlite3_bind_int()- 意味着您将Int 值绑定到语句。函数的第一个参数是要绑定的语句,第二个参数是要绑定的非零索引位置。第三个也是最后一个参数是值本身。此绑定调用返回一个状态代码,但现在您认为它成功了。 3) 执行相同的绑定过程,但这次使用文本值。该调用有两个附加参数;出于本教程的目的,您只需为它们传递-1 和nil 即可。如果您愿意,可以在此处阅读有关绑定参数的更多信息。 4)使用sqlite3_step()函数执行该语句并验证它是否已完成。 5) 与往常一样,完成声明。如果您要插入多个联系人,则可以保留该语句并使用不同的值重复使用它。接下来,通过将以下内容添加到Playground 来调用新方法:
insert() 运行您的Playground 并验证您是否在控制台输出中看到以下内容:
成功插入行。
4. Challenge: Multiple Inserts - 挑战:多个插入
挑战时间!您的任务是更新insert() 以插入到联系人数组中。
提示,在再次执行之前,您需要调用sqlite3_reset() 将已编译的语句重置回其初始状态。
函数插入(){
var insertStatement: 不透明指针?=零
//1
让名称: [NSString]=["雷","克里斯","玛莎","丹妮尔"]
如果sqlite3_prepare_v2(db, insertStatementString, -1, insertStatement, nil)==SQLITE_OK {
//2
for (索引, 名称) in 名称.enumerated() {
//3
让id=Int32(索引+ 1)
sqlite3_bind_int(insertStatement, 1, id)
sqlite3_bind_text(insertStatement, 2, name.utf8String, -1, nil)
如果sqlite3_step(insertStatement)==SQLITE_DONE {
print("成功插入行。")
} 别的{
print("无法插入行。")
}
//4
sqlite3_reset(插入语句)
}
sqlite3_finalize(插入语句)
} 别的{
print("无法准备INSERT 语句。")
}
}如您所见,该代码与您已有的代码非常相似,但有以下显着差异:
1)现在有一个联系人列表而不是常量; 2)每个联系人遍历一次数组; 3) 现在从枚举的索引生成索引,该索引对应于数组中联系人姓名的位置; 4) SQL语句在每次遍历结束时都会重置,以便下一次可以使用它。
5. Querying Contacts - 查询联系人
现在您已经插入了一两行,请确保它们确实有效!
将以下内容添加到游乐场:
let queryStatementString="SELECT * FROM Contact;" 该查询只是从Contact 表中检索所有记录。使用方法将返回所有列。
添加以下方法来执行查询:
函数查询() {
var queryStatement: 不透明指针?=零
//1
如果sqlite3_prepare_v2(db, queryStatementString, -1, queryStatement, nil)==SQLITE_OK {
//2
如果sqlite3_step(queryStatement)==SQLITE_ROW {
//3
让id=sqlite3_column_int(queryStatement, 0)
//4
让queryResultCol1=sqlite3_column_text(queryStatement, 1)
让名称=String(cString: queryResultCol1!)
//5
print("查询结果:")
print("(id) | (名称)")
} 别的{
print("查询没有返回结果")
}
} 别的{
print("无法准备SELECT 语句")
}
//6
sqlite3_finalize(查询语句)
}下面是一步一步的详细解释:
1)准备声明; 2) 执行语句。请注意,您现在正在检查状态代码SQLITE_ROW,这意味着您在单步执行结果时检索了一行; 3) 是时候从返回的行中读取值了。根据您对表结构和查询的了解,您可以逐列访问行的值。第一列是Int,因此您使用sqlite3_column_int() 并传入语句和从零开始的列索引。将返回值分配给本地范围的id 常量; 4) 接下来,从名称列中获取文本值。由于C API,这有点混乱。首先,将值捕获为queryResultCol1,以便可以在下一行将其转换为正确的Swift 字符串; 5)打印结果; 6) 最后是Finalize 语句。现在,通过将以下内容添加到Playground 的底部来调用新方法:
query()运行你的playground,你将在控制台中看到以下输出:
查询结果:
1 |雷W00t!看起来您的数据正在进入数据库!
6. Challenge: Printing Every Row - 挑战:打印每一行
您的任务是更新query() 以打印出表中的每个联系人。
函数查询() {
var queryStatement: 不透明指针?=零
如果sqlite3_prepare_v2(db, queryStatementString, -1, queryStatement, nil)==SQLITE_OK {
while (sqlite3_step(queryStatement)==SQLITE_ROW) {
让id=sqlite3_column_int(queryStatement, 0)
让queryResultCol1=sqlite3_column_text(queryStatement, 1)
让名称=String(cString: queryResultCol1!)
print("查询结果:")
print("(id) | (名称)")
}
} 别的{
print("无法准备SELECT 语句")
}
sqlite3_finalize(查询语句)
请注意,这次不是像以前那样使用单个步骤来检索第一行,而是使用while 循环来执行该步骤,每当返回代码为SQLITE_ROW 时就会发生这种情况。当到达最后一行时,返回代码将传递SQLITE_DONE 并且循环将中断。
7. Updating Contacts - 更新联系人
下一个自然进展是更新现有行。您应该开始看到一种模式正在出现。
首先,创建一个UPDATE 语句:
让updateStatementString="更新联系人SET Name="Chris" WHERE Id=1; "这里你用真实值代替吗?占位符。通常您会使用占位符并执行适当的语句绑定,但为了简洁起见,您可以在此处跳过它。
接下来,将以下方法添加到Playground 中:
函数更新(){
var updateStatement: 不透明指针?=零
如果sqlite3_prepare_v2(db, updateStatementString, -1, updateStatement, nil)==SQLITE_OK {
如果sqlite3_step(updateStatement)==SQLITE_DONE {
print("成功更新行。")
} 别的{
print("无法更新行。")
}
} 别的{
print("无法准备UPDATE 语句")
}
sqlite3_finalize(更新语句)
}这与您之前看到的类似:准备、步骤、完成!将以下内容添加到您的Playground 中:
更新()
query() 这将执行您的新方法,然后调用您之前定义的query() 方法,以便您可以看到结果:
已成功更新行。
查询结果:
1 |克里斯恭喜您更新了第一行!这是多么容易啊。
8. Deleting Contacts - 删除联系人
让我们看看删除您创建的行。同样,您将使用熟悉的准备、步骤和完成。
将以下内容添加到游乐场:
let deleteStatementStirng="从联系人中删除,其中Id=1;"现在添加以下方法来执行该语句:
函数删除() {
var deleteStatement: 不透明指针?=零
如果sqlite3_prepare_v2(db,deleteStatementStirng,-1,deleteStatement,nil)==SQLITE_OK {
如果sqlite3_step(deleteStatement)==SQLITE_DONE {
print("成功删除行。")
} 别的{
print("无法删除行。")
}
} 别的{
print("无法准备DELETE 语句")
}
sqlite3_finalize(删除语句)
}现在你掌握了吗?准备、步骤、最后完成!
执行这个新方法,然后像这样调用query() :
删除()
query() 现在运行您的Playground,您应该在控制台中看到以下输出:
成功删除行。
查询未返回结果注意:如果您完成了上面的“多次插入”挑战,由于表中仍然存在行,因此输出
可能与上面的内容略有不同。9. Handling Errors - 处理错误
到目前为止,希望你已经设法避免SQLite错误。 但是,当你进行没有意义的调用,或者根本无法编译时,错误将会到来。 在发生这些事情时处理错误消息可以节省大量的开发时间,它还使您有机会向用户显示有意义的错误消息。 将以下语句 - 固定格式错误 - 添加到您的playground: let malformedQueryString = "SELECT Stuff from Things WHERE Whatever;"现在添加一个方法来执行这个格式错误的语句: 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) }以下是您将如何强制执行错误: 1) 准备语句,该语句将失败并且不应返回SQLITE_OK;2) 使用sqlite3_errmsg()从数据库中获取错误消息。 此函数返回最近错误的文本描述。 然后,您将错误打印到控制台;3) 一如既往,最终finalize。调用该方法以查看错误消息: prepareMalformedQuery()Run你的playground,您应该在控制台中看到以下输出: Query could not be prepared! no such table: Things嗯,这实际上很有帮助 - 你显然无法在不存在的表上运行SELECT语句!10. Closing the Database Connection - 关闭数据库连接
完成数据库连接后,您将负责关闭它。 但请注意 - 在成功关闭数据库之前,必须执行许多操作,如SQLite documentation中所述。 调用close函数,如下所示: sqlite3_close(db)Run你的playground;您应该在playground的右侧结果视图中看到状态代码0;这表示SQLITE_OK,这意味着您的关闭调用成功。 您已经成功创建了一个数据库,添加了一个表,向表中添加了行,查询并更新了这些行,甚至删除了一行 - 所有这些都使用了Swift的SQLite C API。 很好! 在下一节中,您将利用所学内容,并了解如何在Swift中包含其中一些调用。好了,文章到这里就结束啦,如果本次分享的深入数据存储策略:SQLite基础持久化方案详解(第一部分)和问题对您有所帮助,还望关注下本站哦!
【深入数据存储策略:SQLite基础持久化方案详解(第一部分)】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
终于有人写了关于数据持久化的文章!
有8位网友表示赞同!
感觉学习一下 SQLite 持久化方案挺有帮助的,特别是对于小型项目。
有7位网友表示赞同!
想了解不同数据持久化方案的优缺点,这篇文章看起来很有潜力。
有6位网友表示赞同!
从题目中看应该讲解基本的原理吧?适合入门的人看。
有16位网友表示赞同!
SQLite 的用场景比较广泛啊,这篇示例很有参考价值。
有15位网友表示赞同!
希望这篇文章能详细介绍如何实现数据持久化过程,不要太抽象。
有19位网友表示赞同!
我一直想尝试使用 SQLite 来存储数据,看看这篇文章能不能给我一些启发。
有19位网友表示赞同!
简单易用的持久化方案非常吸引人,期待看更多关于 SQLite 的内容。
有12位网友表示赞同!
我正在项目中需要用到数据持久化的方式,这篇文章正好可以借鉴一下。
有20位网友表示赞同!
对于初学者来说,一个浅显易懂的示例能更有帮助吧?
有15位网友表示赞同!
期待作者能后续分享更多不同类型的持久化方案,扩展知识面。
有18位网友表示赞同!
数据持久化是程序开发中必不可少的知识,感谢作者的分享!
有14位网友表示赞同!
这篇文章能够让我更好地理解数据持久化的概念和应用场景吗?
有20位网友表示赞同!
希望能从实践角度讲解,用代码示例展示如何实现持久化功能。
有9位网友表示赞同!
SQLite 作为轻量级数据库,确实适合一些小型项目使用,这篇示例挺有意义的。
有5位网友表示赞同!
文章应该对不同类型的持久化方案进行对比分析吗?
有18位网友表示赞同!
对于数据安全的考虑也很重要,希望这篇文章能提到相关的点。
有12位网友表示赞同!
学习新的技术永远没有错,期待阅读这篇文章并从中获得收获。
有14位网友表示赞同!