Introducción a FMDB
FMDB es un contenedor de Objective-C basado en SQLite. Es de código abierto y fácil de configurar. Se puede decir que es la única biblioteca tan buena. (Si conoce otras bibliotecas mejores, deje un mensaje para compartir conmigo, ¡me encantaría probarlo también!)
configurar
Vamos a crear un nuevo proyecto Xcode, lo llamé SQLiteIntro
.
Esta aplicación no será muy complicada, porque solo quiero presentar brevemente SQLite, para que todos puedan entender fácilmente cómo usar SQL en proyectos Swift.
envoltura
Deberíamos mantener el buen hábito de separar la lógica en categorías o estructuras dedicadas. En este ejemplo, estamos usando una base de datos SQL, por lo que necesitamos crear una clase para abstraer parte de la lógica de la capa de datos para que el código sea más conciso.
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
}
}
复制代码
Este es el comienzo de nuestro código, ¡bastante simple!
El código es bastante sencillo: después DataWrapper
de crear la clase por primera vez, busca el archivo de la base de datos y, si el archivo no existe, FMDB crea una base de datos en esa ruta. Finalmente, abre la conexión a la base de datos y crea la user
tabla.
Modelo
A continuación, construiremos una User
estructura para manejar los registros de la base de datos. En la aplicación de muestra, agregaremos otro contenido relacionado con JSON. Usaremos algunas API web más adelante para crear un usuario con nombre aleatorio.
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)
}
}
复制代码
Cada vez que hacemos una consulta desde la base de datos, incluso si solo hay un resultado, o ningún resultado, obtenemos uno FMResultSet
. Entonces, en este caso, una función de inicio dedicada es útil para manejar toda la lógica de configuración por nosotros.
Combinar 和 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 和不同的版本。
这里也推荐一些面试相关的内容!