How to write a better data access layer with Realm

两盒软妹~` 提交于 2020-02-21 11:18:44

问题


I've been using Realm in a few small projects and I quite like it. I'm hoping to move on to using it in bigger projects and I'm looking for better structure my data access layer.

I came across this similar question and tried to build up on the information I found there. The approach discussed there is the DAO pattern so I gave a shot at that.

This is my model class.

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()
        // ...
    }
}

Then I created a ChatDAOProtocol to hold all the convenience helper methods.

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?
}

Lastly I created another class called ChatHelper that implemented all those protocol methods.

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
    }

}

This already seems better than sprinkling all the database related code all over the VCs and stuff. But I still have some doubts.

For example, say if I need to get all the participants of a chat, now I have to call the method on the ChatHelper class. And if I want to get simply the chat title, I call the title property of the Chat object itself. Doesn't seem like a very unified interface. Should I include getters and setters for all the properties in the helper as well. So the Chat object is never directly called (except for maybe creating an instance).

Or

Should I make the Chat object itself conform to the ChatDAOProtocol protocol? So all the convenience methods as well as the properties are directly accessible from the Chat object straight up?

Or is there a better way than both of these?


回答1:


This is pretty tricky sort of question since it really depends on how much you want to abstract away from directly interacting with Realm, and how much you want to compromise with Realm's performance.

Personally, I think it is fine if you are abstracting away query and write logic, but still directly reading from Realm model objects. If you moved to another object-based database (Like Core Data), then while you would refactor the parent class these objects belonged to something else (eg, RLMObject to NSManagedObject), the way your business logic read from these objects wouldn't change.

One thing you definitely need to be careful though is abstracting the logic in such a way that will utilize Realm very inefficiently.

The main example of this I can see is in your getParticipants method, you're returning a standard Swift array. Converting a Realm Results object to such would result in paging every object in memory (as opposed to lazy-loading on request), so you would lose a lot of Realm performance benefits. But since Results objects behave like standard arrays, you wouldn't need to change your business logic if you directly returned one.

Another consideration: if you're updating a single property on a batch of objects, you would be much better off ensuring all of the objects are updated in a single write transaction, instead of the helper class internally opening a write transaction each time the helper method is called.




回答2:


I am new to Realm but I have written this code to have a Generic way to access my objects. I still have some issues to access this methods from background threads, but I hope it helps!

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
    }
  }

}

I forgot to show my GenericDAO protocol:

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()

}

To create an instance:

let movieDAO = GenericDAOImpl<Movie>()

I am still working to see if this is the best approach, but anyway It helped me a lot.



来源:https://stackoverflow.com/questions/41975550/how-to-write-a-better-data-access-layer-with-realm

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!