使用领域的简单iOS数据持久性
为什么是境界?
让我们从一些介绍开始。 Realm移动数据库是为移动应用程序设计的跨平台移动数据库解决方案,它具有许多优势,例如,它快速,易于设置并且需要更少的样板代码。 而且,Realm对象服务器已于去年发布,也许将来我会尝试macOS版本。
模式与模型
为简单起见,在此演示中我仅使用一个名为BookObject
表,该表包含四个属性,包括bookID
, name
, comment
和rating
。 以下是我的领域对象的定义。
final class BookObject: Object {
dynamic var bookID: String = UUID().uuidString
dynamic var name: String = ""
dynamic var comment: String? = nil
dynamic var rating: Int = 0
}
由于线程安全和关注点分离,我定义了另一个不变的模型结构,称为Book
并使用它来在数据库中显示每本书的信息。
struct Book {
enum RatingScale: Int {
case notRecommended = 0
case mediocre
case good
case veryGood
case outstanding
}
let bookID: String
let name: String
let comment: String?
let rating: RatingScale
}
这是一个好习惯,因为它消除了我的视图控制器和BookObject
之间的知识。 但是,这也带来了一点点复杂性:我需要将BookObject
转换为Book
, BookObject
。
数据处理
让我们搁置转换并首先实现我的Database
类。 它包含一个Realm数据库实例,并使用该实例处理数据操作。 同样,我的视图控制器不应该知道Realm数据库实例,因此我将其设置为私有属性,并通过依赖注入来分配该属性。
final class Database {
private let realm: Realm
init(realm: Realm = try! Realm()) {
self.realm = realm
}
}
一般来说,我将在Database
的CRUD方法中实现Book
和BookObject
之间的转换,例如-createBook
或-fetchBook
。 但是,这不是解决这种情况的快捷方法,因为这些方法将与Book
和BookObject
高度结合。 如果有一个新模型,则这些方法无法重用,因此有必要为新模型本身添加一堆新方法。 解决此问题的更好方法是引入闭包作为CRUD方法的参数,这些闭包负责模型和Realm对象之间的转换。
// Inside Database class
func createOrUpdate(model: Model, with reverseTransformer: (Model) -> RealmObject) {
let object = reverseTransformer(model)
try! realm.write {
realm.add(object, update: true)
}
}
func fetch(with predicate: NSPredicate?, sortDescriptors: [SortDescriptor], transformer: (Results) -> Model) -> Model {
var results = realm.objects(RealmObject.self)
if let predicate = predicate {
results = results.filter(predicate)
}
if sortDescriptors.count > 0 {
results = results.sorted(by: sortDescriptors)
}
return transformer(results)
}
func delete(type: RealmObject.Type, with primaryKey: String) {
let object = realm.object(ofType: type, forPrimaryKey: primaryKey)
if let object = object {
try! realm.write {
realm.delete(object)
}
}
}
此外,我还创建了两个自定义的Book
和BookObject
初始化BookObject
,以实际处理转换。
extension BookObject {
convenience init(book: Book) {
self.init()
// Assign properties here
}
}
extension Book {
init(object: BookObject) {
// Assign properties here
}
}
最后,为了使我的代码库更具可读性和简洁性,我提取了-fetch
方法的参数并定义了一个名为FetchRequest
的新结构。 因此,我能够编写Book
的扩展并生成所需的请求。
struct FetchRequest {
let predicate: NSPredicate?
let sortDescriptors: [SortDescriptor]
let transformer: (Results) -> Model
}
extension Book {
static let all = FetchRequest(predicate: nil, sortDescriptors: [], transformer: { $0.map(Book.init) })
}
测试中
因为我通过Carthage将Realm和RealmSwift集成到我的项目中,所以在编写一些测试之前,必须将变量添加到测试目标的“ 构建设置 ”中的“ 框架搜索路径 ”中。
使用真实的Realm数据库实例进行测试非常危险,因为它将破坏真实的数据。 幸运的是,Realm通过设置inMemoryIdentifier
而不是fileURL
上的Realm.Configuration
提供内存数据库实例。
class DatabaseTests: XCTestCase {
var database: Database!
override func setUp() {
super.setUp()
let realm = try! Realm(configuration: Realm.Configuration(inMemoryIdentifier: "com.shenghuawu.DataPersistence"))
database = Database(realm: realm)
}
}
在使用内存中的Realm数据库实例创建数据库实例之后,让我们编写一些有关Book创建和更新的测试。
// Inside DatabaseTests class
func testBookCreation() {
let newBook = Book.bigLittleLies
database.createOrUpdate(model: newBook, with: BookObject.init)
// Verify with assertions
}
func testBookUpdating() {
let newBook = Book.fake
database.createOrUpdate(model: newBook, with: BookObject.init)
let bookInDB = database.fetch(with: Book.all).first!
let modifiedBook = Book(bookID: bookInDB.bookID, name: "New Name", comment: "Change the rating and the name of this book.", rating: .mediocre)
database.createOrUpdate(model: modifiedBook, with: BookObject.init)
// Verify with assertions
}
结论
示例项目在这里。 数据持久性在每个iOS应用程序中都扮演着至关重要的角色,Realm为此提供了理想的解决方案。 我希望本文给出一个简单的概念,说明如何使用Realm实现数据持久性。 但是,仍然有几处可以改进的地方,例如,处理每个数据库写入事务中的错误或利用Realm.Configuration的优势将数据库与不同的用户分开。 如果您对本文有任何意见或疑问,请在下面留下答复。 谢谢!