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



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

我遇到了这个类似的问题,并试图根据我在那里找到的信息进行总结。这里讨论的方法是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对象访问吗?

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

我是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的直接交互中抽象出多少,以及你想在多大程度上损害Realm的性能。

就我个人而言,我认为如果您抽象掉查询和写入逻辑,但仍然直接从Realm模型对象中读取,这是可以的。如果您移动到另一个基于对象的数据库(如核心数据),则在重构父类时,这些对象属于其他对象(例如,RLMObjectNSManagedObject),您的业务逻辑从这些对象读取的方式不会改变。

不过,有一件事你肯定需要小心,那就是以一种低效利用Realm的方式抽象逻辑。

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

另一个考虑因素是:如果要更新一批对象的单个属性,最好确保所有对象都在一个写事务中更新,而不是每次调用helper方法时,helper类都在内部打开一个写交易。

最新更新