使用领域的简单iOS数据持久性

为什么是境界?

让我们从一些介绍开始。 Realm移动数据库是为移动应用程序设计的跨平台移动数据库解决方案,它具有许多优势,例如,它快速,易于设置并且需要更少的样板代码。 而且,Realm对象服务器已于去年发布,也许将来我会尝试macOS版本。

模式与模型

为简单起见,在此演示中我仅使用一个名为BookObject表,该表包含四个属性,包括bookIDnamecommentrating 。 以下是我的领域对象的定义。

 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转换为BookBookObject

数据处理

让我们搁置转换并首先实现我的Database类。 它包含一个Realm数据库实例,并使用该实例处理数据操作。 同样,我的视图控制器不应该知道Realm数据库实例,因此我将其设置为私有属性,并通过依赖注入来分配该属性。

 final class Database { 
private let realm: Realm
  init(realm: Realm = try! Realm()) { 
self.realm = realm
}
}

一般来说,我将在Database的CRUD方法中实现BookBookObject之间的转换,例如-createBook-fetchBook 。 但是,这不是解决这种情况的快捷方法,因为这些方法将与BookBookObject高度结合。 如果有一个新模型,则这些方法无法重用,因此有必要为新模型本身添加一堆新方法。 解决此问题的更好方法是引入闭包作为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)
}
}
}

此外,我还创建了两个自定义的BookBookObject初始化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的优势将数据库与不同的用户分开。 如果您对本文有任何意见或疑问,请在下面留下答复。 谢谢!