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) 创立一个数据库。最终,它会翻开数据库的连接,并创立usertable。

模型 (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,添加@PublishedUser 阵列,这样就可以在一个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
    }
}

然后,把这段程式码放在DataWrapperinit方法的最终:

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,会看到一个空的列表。但只要点击右上的加号,就可以在数据库中加入内容,而列表的称号也会实时在你的列表中呈现。

在 iOS 应用 SQLite 来处理数 - 提高 App 效能

总结

这篇文章是一个十分简略的典范,用另一种方式来在熟悉的 SQLite 数据库中存储数据,你可以看到 App 的效能比 CoreData 版别大大提高。

假如你想更好地控制数据,SQLite 和 SQL 绝对不会让你绝望!对於需求精细控制和查询优化器 (query optimization) 的 App 来说,SQLite 可以大大提高效能。运用 CloudKit 同步数据也会变得更简略,因為现在咱们只需求同步 SQLite 档案,而无需处理其他 CoreData Table 和不同的版别。

这里也推荐一些面试相关的内容!

  • ① BAT等各个大厂iOS面试真题+答案大全

  • ② iOS中高档开发必看的抢手书本(经典必看)

  • ③ iOS开发高档面试”简历制作“辅导

  • ④ iOS面试流程到基础知识大全