如何使用 Realm 编写更好的数据访问层

Posted

技术标签:

【中文标题】如何使用 Realm 编写更好的数据访问层【英文标题】:How to write a better data access layer with Realm 【发布时间】:2017-06-17 22:48:33 【问题描述】:

我一直在一些小项目中使用 Realm,我非常喜欢它。我希望继续在更大的项目中使用它,并且我正在寻找更好的数据访问层结构。

我遇到了类似的question,并试图根据我在那里找到的信息进行构建。那里讨论的方法是 DAO 模式,所以我试了一下。

这是我的模型类。

class Chat: Object 
    dynamic var id: String = ""
    dynamic var createdAt: Date = Date()
    dynamic var creatorId: String = ""
    dynamic var title: String?
    let chatMessages = List<ChatMessage>()

    override static func primaryKey() -> String? 
        return "id"
    

    convenience init(fromJSON json: JSON) 
        self.init()
        // ...
    

然后我创建了一个ChatDAOProtocol 来保存所有便利的辅助方法。

protocol ChatDAOProtocol 
    func addMessage(_ message: ChatMessage)
    func getChatThumbnail() -> UIImage
    func getParticipants(includingMe: Bool) -> [Participant]?
    static func getChat(fromId id: String) -> Chat?
    static func getChat(fromCreatorId id: String) -> Chat?

最后,我创建了另一个名为 ChatHelper 的类,它实现了所有这些协议方法。

class ChatHelper: ChatDAOProtocol 
    func addMessage(_ message: ChatMessage) 

    

    func getChatThumbnail() -> UIImage 
        return UIImage()
    

    func getParticipants(includingMe: Bool) -> [Participant]? 
        return nil
    

    static func getChat(fromId id: String) -> Chat? 
        return nil
    

    static func getChat(fromCreatorId id: String) -> Chat? 
        return nil
    


这似乎比在 VC 和其他东西上散布所有与数据库相关的代码要好。但我还是有些疑惑。

例如,假设我需要获取聊天的所有参与者,现在我必须调用 ChatHelper 类上的方法。如果我只想获得聊天标题,我调用Chat 对象本身的title 属性。似乎不是一个非常统一的界面。我是否也应该在帮助器中包含所有属性的 getter 和 setter。所以Chat 对象永远不会被直接调用(除了可能创建一个实例)。

或者

我应该让Chat 对象本身符合ChatDAOProtocol 协议吗?那么所有的便捷方法和属性都可以直接从Chat 对象直接访问?

或者有比这两种方法更好的方法吗?

【问题讨论】:

【参考方案1】:

这是一个相当棘手的问题,因为它实际上取决于您希望从与 Realm 的直接交互中抽象出多少,以及您希望在多大程度上与 Realm 的性能妥协。

就个人而言,我认为如果您将查询和编写逻辑抽象出来,但仍直接从 Realm 模型对象中读取,这很好。如果您移动到另一个基于对象的数据库(如 Core Data),那么当您重构父类时,这些对象属于其他对象(例如,RLMObjectNSManagedObject),您的业务逻辑从这些对象中读取的方式不会改变。

您绝对需要小心的一件事是以一种非常低效地利用 Realm 的方式抽象逻辑。

我可以看到的主要示例是在您的 getParticipants 方法中,您正在返回一个标准的 Swift 数组。将 Realm Results 对象转换为此类将导致对内存中的每个对象进行分页(与请求延迟加载相反),因此您将失去很多 Realm 性能优势。但是由于Results 对象的行为类似于标准数组,因此如果直接返回一个,则无需更改业务逻辑。

另一个考虑因素:如果您要更新一批对象的单个属性,最好确保所有对象都在单个写入事务中更新,而不是帮助器类在内部分别打开一个写入事务调用辅助方法的时间。

【讨论】:

感谢您的回复。我会考虑您提到的几点并重新编写我的代码。那么你认为在同一个 Chat 类中使用辅助方法更好,还是为这些辅助方法单独创建一个 ChatHelper 类更好?【参考方案2】:

我是 Realm 的新手,但我已经编写了这段代码,以便有一种通用的方式来访问我的对象。我仍然在从后台线程访问此方法时遇到一些问题,但我希望它有所帮助!

import Foundation
import RealmSwift


class GenericDAOImpl <T:Object> : GenericDAO 

  //  MARK: setup

  var realm: Realm?
  init() 
    do 
      realm = try Realm()
     catch 
      logger.error("Realm Initialization Error: \(error)")
    
  

  //   MARK: protocol implementation

  func save(_ object: T) -> Bool 
    guard let `realm` = realm else 
      return false
    

    do 
      try realm.write 
        realm.add(object)
      
     catch 
      return false
    

    return true
  

  func saveAll(_ objects: [T]) -> Int 
    var count = 0
    for obj in objects 
      if save(obj)  count += 1 
    
    return count
  

  private func findAllResults() -> Results<T>? 
    return realm?.objects(T.self)
  

  func findAll() -> [T] 
    guard let res = findAllResults() else  return [] 
    return Array(res)
  

  func findByPrimaryKey(_ id: Any) -> T? 
    return self.realm?.object(ofType: T.self, forPrimaryKey: id)
  

  func deleteAll() 
    guard let res = findAllResults() else  return 

    do 
      try realm?.write 
        self.realm?.delete(res)
      
     catch 
      logger.error("Realm Error Deleting Objects: \(error)")
      return
    
  


我忘了显示我的 GenericDAO 协议:

protocol GenericDAO 
  associatedtype T:Object

  func save(_ object: T) -> Bool

  func saveAll(_ objects: [T]) -> Int

  func findAll() -> [T]

  func findByPrimaryKey(_ id: Any) -> T?

  func deleteAll()


创建实例:

let movieDAO = GenericDAOImpl<Movie>()

我仍在努力看看这是否是最好的方法,但无论如何它对我帮助很大。

【讨论】:

以上是关于如何使用 Realm 编写更好的数据访问层的主要内容,如果未能解决你的问题,请参考以下文章

day66(YAML配置,使用Druid数据库连接池,编写持久层(数据访问层)代码,关于业务逻辑层(service层)

如果我需要用户可以访问数百个其他用户的数据,我可以使用Realm DB吗?

如何从 .NET 用户界面将 MySQL 连接信息发送到数据访问层

如何在 Java 中对 DAO 应用单元测试

1.1 DAL数据访问层

由 dbml 形成的数据访问层中的功能