在 iOS 应用 SQLite 来处理数 - 提高 App 效能
FMDB 简介
FMDB 是一个建基於 SQLite 的 Objective-C 包装器 (wrapper),它是开源的,而且设置非常容易,可以说是一个这麼好的程式库 (Library)。(如果你知道有其他更好的程式库,欢迎留言与我分享,我也很想试试使用!)
设置
让我们创建一个新的 Xcode 专案,我把它命名為 SQLiteIntro
。
这个 App 不会很复杂,因為我只是想简单介绍 SQLite,让大家简单了解如何在 Swift 专案中使用 SQL。
包装器
我们应该保持一个好习惯,就在专用的类别或结构中分开逻辑。在这个范例中,我们使用的是 SQL 数据库 (database),因此我们要创建一个类别来抽像化 (abstract) 一些数据层逻辑,让程式码更加简洁。
final class DataWrapper: ObservableObject {
private let db: FMDatabase
init(fileName: String = "test") {
// 1 - Get filePath of the SQLite file
let fileURL = try! FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("\(fileName).sqlite")
// 2 - Create FMDatabase from filePath
let db = FMDatabase(url: fileURL)
// 3 - Open connection to database
guard db.open() else {
fatalError("Unable to open database")
}
// 4 - Initial table creation
do {
try db.executeUpdate("create table if not exists users(username varchar(255) primary key, age integer)", values: nil)
} catch {
fatalError("cannot execute query")
}
self.db = db
}
}
复制代码
这就是我们程式码的开头了,很简单吧!
程式码非常直接:在 DataWrapper
类别初次被创建后,它就会查找数据库档案,如果档案不存在,FMDB 就会以该路径 (path) 创建一个数据库。后,它会打开数据库的连接,并创建 user
table。
模型 (Model)
接下来我们会建立一个 User
结构,来处理数据库纪录。在范例 App 中,我们会添加一些其他与 JSON 相关的内容。我们稍后会使用一些 Web API 来创建一些随机名称的 User。
struct User: Hashable, Decodable {
let username: String
let age: Int
init(username: String, age: Int) {
self.username = username
self.age = age
}
init?(from result: FMResultSet) {
if let username = result.string(forColumn: "username") {
self.username = username
self.age = Int(result.int(forColumn: "age"))
} else {
return nil
}
}
private enum CodingKeys : String, CodingKey {
case username = "first_name"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
username = try container.decode(String.self, forKey: .username)
age = Int.random(in: 1..<100)
}
}
复制代码
每当我们从 database 进行查询时,即使结果只有一个,或是没有结果,我们都会得到一个 FMResultSet
。因此,在这种情况下一个专用的 init 函式就十分有用,可以帮我们处理所有设置逻辑。
Combine 和 MVVM
因為我使用的是 SwiftUI,我希望 DataWrapper
可以是响应式 (reactive) 的,并就数据库中可能发生的变化通知视图。 让我们回到 DataWrapper
,添加 @Published
User 阵列,这样就可以在一个 List
中显示 User。
final class DataWrapper: ObservableObject {
private let db: FMDatabase
@Published var users = [User]()
...
}
复制代码
我们想要从数据库中获取 User,并在数据库打开后立即进行发佈。因此,我们需要创建一个方法来查询所有 User,并在数据库初始化后,将它们设置為 DataWrapper
的 Users 变数。
func getAllUsers() -> [User] {
var users = [User]()
do {
let result = try db.executeQuery("select username, age from users", values: nil)
while result.next() {
if let user = User(from: result) {
users.append(user)
}
}
return users
} catch {
return users
}
}
复制代码
然后,把这段程式码放在 DataWrapper
的 init
方法的后:
users = getAllUsers()
复制代码
现在,当我们次啟动 DataWrapper
时,DataWrapper
就会自动获取所有 User,而且这些 User 是可用於 SwiftUI 的。
接著,让我们建立一个 insert
函式。我们稍后会用到它。
func insert(_ user: User) {
do {
try db.executeUpdate(
"""
insert into users (username, age)
values (?, ?)
""",
values: [user.username, user.age]
)
users.append(user)
} catch {
fatalError("cannot insert user: \(error)")
}
}
复制代码
简单的 SwiftUI 视图
我想创建一个 List
,来显示数据库中的所有使用者,并创建一个简单的函式,来向 Web API 获取随机的使用者名称,并将新使用者插入到数据库。
struct ContentView: View {
@EnvironmentObject var db: DataWrapper
var body: some View {
NavigationView {
List(db.users, id: \.self) { user in
HStack {
Text(user.username)
Spacer()
Text("\(user.age)")
}
}
.navigationTitle("Users")
.toolbar {
ToolbarItem(id: "plus", placement: .navigationBarTrailing, showsByDefault: true) {
Button(action: {
createRandomUser()
}, label: {
Image(systemName: "plus")
})
}
}
}
}
private func createRandomUser() {
let url = URL(string: "[https://random-data-](https://random-data-api.com/api/name/random_name)
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else {
fatalError("No data")
}
DispatchQueue.main.async {
let user = try! JSONDecoder().decode(User.self, from: data)
db.insert(user)
}
}
task.resume()
}
}
复制代码
如果我们现在执行 App,会看到一个空的列表。但只要点击右上的加号,就可以在数据库中加入内容,而列表的名称也会实时在你的列表中出现。
总结
这篇文章是一个非常简单的范例,用另一种方式来在熟悉的 SQLite 数据库中存储数据,你可以看到 App 的效能比 CoreData 版本大大提高。
如果你想更好地控制数据,SQLite 和 SQL 不会让你失望!对於需要精密控制和查询优化器 (query optimization) 的 App 来说,SQLite 可以大大提高效能。使用 CloudKit 同步数据也会变得更容易,因為现在我们只需要同步 SQLite 档案,而无需处理其他 CoreData Table 和不同的版本。
相关文章