问题
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