在
Internet上 ,甚至在
Habré上 ,都有关于如何使用Realm的大量文章。 该数据库非常方便,并且如果可以使用它,则只需很少的精力即可编写代码。 本文将介绍我来到的工作方法。
问题所在
代码优化
显然,每次为Realm对象编写初始化代码并调用相同的函数来读写对象都是不方便的。 您可以将其抽象包装。
示例数据访问对象:
struct DAO<O: Object> { func persist(with object: O) { guard let realm = try? Realm() else { return } try? realm.write { realm.add(object, update: .all) } } func read(by key: String) -> O? { guard let realm = try? Realm() else { return [] } return realm.object(ofType: O.self, forPrimaryKey: key) } }
用法:
let yourObjectDAO = DAO<YourObject>() let object = YourObject(key) yourObjectDAO.persist(with: object) let allPersisted = yourObjectDAO.read(by: key)
您可以向DAO添加许多有用的方法,例如:删除,读取相同类型,排序等的所有对象。所有这些方法都可以与任何Realm对象一起使用。
从错误的线程访问
Realm是线程安全的数据库。 由此带来的主要不便之处是无法将Realm.Object类型的对象从一个线程转移到另一个线程。
代码:
DispatchQueue.global(qos: .background).async { let objects = yourObjectDAO.read(by: key) DispatchQueue.main.sync { print(objects) } }
它将给出一个错误:
Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.'
当然,您可以始终在一个线程中使用该对象,但是实际上这会带来某些困难,可以更好地规避。
对于该解决方案,将Realm.Object转换为将在不同线程之间静默传输的结构是“方便的”。
领域对象:
final class BirdObj: Object { @objc dynamic var id: String = "" @objc dynamic var name: String = "" override static func primaryKey() -> String? { return "id" } }
结构:
struct Bird { var id: String var name: String }
要将对象转换为结构,我们将使用协议实现
译者:
protocol Translator { func toObject(with any: Any) -> Object func toAny(with object: Object) -> Any }
对于Bird,它将如下所示:
final class BirdTranslator: Translator { func toObject(with any: Any) -> Object { let any = any as! Bird let object = BirdObj() object.id = any.id object.name = any.name return object } func toAny(with object: Object) -> Any { let object = object as! BirdObj return Bird(id: object.id, name: object.name) } }
现在仍然需要稍微更改DAO,以便它接受并返回结构,而不是Realm对象。
struct DAO<O: Object> { private let translator: Translator init(translator: Translator) { self.translator = translator } func persist(with any: Any) { guard let realm = try? Realm() else { return } let object = translator.toObject(with: any) try? realm.write { realm.add(object, update: .all) } } func read(by key: String) -> Any? { guard let realm = try? Realm() else { return nil } if let object = realm.object(ofType: O.self, forPrimaryKey: key) { return translator.toAny(with: object) } else { return nil } } }
该问题似乎已解决。 现在,DAO将返回一个Bird结构,该结构可以在线程之间自由移动。
let birdDAO = DAO<BirdObj>(translator: BirdTranslator()) DispatchQueue.global(qos: .background).async { let bird = birdDAO.read(by: key) DispatchQueue.main.sync { print(bird) } }
大量相同类型的代码。
解决了在线程之间传输对象的问题之后,我们遇到了一个新问题。 即使在最简单的情况下,对于具有两个字段的类,我们也需要编写额外的18行代码。 试想一下,如果没有2个字段(例如10个),并且其中一些不是基本类型,而是需要转换的实体。 所有这些都创建了一堆相同类型的代码。 数据库中数据结构的小变化迫使您爬到三个位置。
本质上,每个实体的代码始终是相同的。 区别仅取决于结构领域。
您可以编写自动生成,通过为每一个发布Realm.Object和Translator来解析我们的结构。
Sourcery可以为此提供帮助。 关于
habra的文章已经有关于
模拟的帮助。
为了充分掌握该工具,我对
模板标签和过滤器 模板 (基于制作Sourcery的
模板 )和
Sourcery本身的文档进行了足够的描述。
在我们的特定示例中,Realm.Object的生成可能如下所示:
import Foundation import RealmSwift #1 {% for type in types.structs %} #2 final class {{ type.name }}Obj: Object { #3 {% for variable in type.storedVariables %} {% if variable.typeName.name == "String" %} @objc dynamic var {{variable.name}}: String = "" {% endif %} {% endfor %} override static func primaryKey() -> String? { return "id" } } {% endfor %}
#1-我们遍历所有结构。
#2-我们为每个对象创建自己的继承程序类Object。
#3-对于每个类型名称==字符串的字段,创建一个具有相同名称和类型的变量。 在这里,您可以为原语(例如Int,Date和更复杂的原语)添加代码。 我认为本质很明确。
生成Translator的代码看起来相似。
{% for type in types.structs %} final class {{ type.name }}Translator: Translator { func toObject(with entity: Any) -> Object { let entity = entity as! {{ type.name }} let object = {{ type.name }}Obj() {% for variable in type.storedVariables %} object.{{variable.name}} = entity.{{variable.name}} {% endfor %} return object } func toAny(with object: Object) -> Any { let object = object as! {{ type.name }}Obj return Bird( {% for variable in type.storedVariables %} {{variable.name}}: object.{{variable.name}}{%if not forloop.last%},{%endif%} {% endfor %} ) } } {% endfor %}
最好通过依赖项管理器安装Sourcery,并指明版本,以便您编写的内容以相同的方式适用于所有人,并且不会损坏。
安装后,我们需要编写一行bash代码以在BuildPhase项目中运行它。 必须在项目文件开始编译之前生成它。

结论
我给的例子非常简化。 显然,在大型项目中,.stencil之类的文件将更大。 在我的项目中,它们占用不到200行,而生成4000行,并且除其他外,还增加了Realm中多态的可能性。
通常,由于某些对象转换为其他对象,我没有遇到延迟。
我将很高兴收到任何反馈和批评。
参考文献
境界迅捷源码github原始资料模具内置模板标签和过滤器快速了解Sourcery使用Realm和Swift创建ToDo应用程序