Realm 对象的 JSON 编码器 / 解码器
RealmCoder 对象允许你轻松地从 JSON 数据块中解码 Realm 对象。它也可以反过来,将 Realm 对象转换为 JSON 数据。 RealmCoder 的一个关键特性是它允许对 Realm 进行增量更新。 解码包含部分数据的 JSON 块会保留其他属性不变。
大多数 iOS 应用程序都是通过 REST/JSON 访问的网络服务器的客户端。 在 Oak City Labs,我们使用 Realm 作为本地数据库来缓存来自服务器的数据,以便应用程序可以更快地响应并仍然可以离线运行。(有时我们也缓存信息以进行上传。)这意味着每个应用程序都需要从服务器检索 JSON 数据并将其写入 Realm 数据库。 这是共享库的绝佳机会。
这里有一个棘手的地方。我们需要增量更新我们的 Realm 对象。 有时我们无法从服务器获取有关对象的所有数据。 想象一下餐厅的主-详视图。 在主视图中,你有一个餐厅列表。 点击一家餐厅,你就可以深入到餐厅的详细信息页面。
我们希望主列表速度很快,因此服务器响应包括列表中显示的摘要信息,例如餐厅的名称,但不包括电话号码。 加载详细信息页面时,我们会请求完整信息,包括电话号码。 当你返回到主列表时,你不希望刷新摘要数据来清除缓存中存储的详细信息。
如果我们小心谨慎,Realm 可以很容易地进行这些增量更新。 只要我们有一个仅包含更新值的字典,Realm 就可以对具有主键的任何对象进行部分更新。
这就是 RealmCoder 的作用。 通过使用对象的 Realm 模式,我们可以将属性与 JSON 键匹配,并以类型安全的方式解析数据。 使用解析后的数据,我们构建一个“更新”字典并将其应用于 Realm 数据库。 一切都在运行时发生,并且可以自动与任何 Realm 对象一起使用。 使用 Realm Object 子类上的类变量,你可以控制名称映射、忽略字段等。 如果你有 Codable 的经验,那么 RealmCoder 应该会让你感到熟悉。
你可以通过将 Realm 对象传递给 init 函数来创建一个 RealmCoder 对象。
let coder = RealmCoder(realm: realm)
RealmCoder 可以直接使用 Realm 对象,无需对对象类进行特殊修改。 这个简单的 User 类可以自动与 RealmCoder 一起使用。
class User: Object {
@objc dynamic var objId: String = ""
@objc dynamic var rank: Int = -1
}
将 JSON 数据解码为 Realm 对象是一个简单的一行调用,就像 Codable 一样。 decode 调用返回一个已经提交到 Realm 的对象。 如果该类具有主键并且已经存在具有相同主键的对象,则 decode 方法将仅更新 JSON 数据中包含的字段,而保留其他属性不变。
let user = try coder.decode(User.self, from: jsonData)
编码 JSON 也很简单。
let jsonData = try coder.encode(user)
请注意,这些方法可能会抛出异常。 与底层 Realm 的事务可能会抛出异常,并且这些异常会被向上传播。 此外,当 Realm 对象的模式与给定的数据不匹配时,RealmCoder 可能会遇到问题。 例如,对象将一个属性定义为 Int
,但 JSON 值是 String
。
将所有这些放在一起,从 JSON 数据中声明一个模型并在数据库中创建一个 Realm 对象非常简单。
// Declare the model
class User: Object {
@objc dynamic var objId: String = ""
@objc dynamic var rank: Int = -1
}
// Create a RealmCoder
let coder = RealmCoder(realm: realm)
// Decode some data and commit it to the realm
let user = try coder.decode(User.self, from: jsonData)
print("Decode User with objId: \(user.objId)")
// Make some modifications to the `user` object in
// the usual Realm ways.
// Encode the object to send to the server
let modifiedData = try coder.encode(user)
对象属性名称和 JSON 键之间总是存在不匹配。 在 RealmCoder 中,默认假设是属性名称和键完全匹配。 如果不是这种情况,你可以使用名为 realmCodableKeys
的类变量创建自己的映射。 此 String
到 String
的映射应在左侧具有对象的属性名称,在右侧具有 JSON 键名称。 让我们向我们的 User
对象添加几个字段。
class User: Object {
@objc dynamic var objId: String = ""
@objc dynamic var username: String = ""
@objc dynamic var firstName: String = ""
@objc dynamic var lastName: String = ""
}
我们需要在驼峰式 swift 名称和 JSON 中的蛇形命名之间进行转换。 此外,让我们将 JSON 键 id
映射到属性名称 objId
。 转换很容易在类属性中定义。 任何未列出的属性名称都假定在 JSON 键中具有相同的名称。
override class var realmCodableKeys: [String: String] {
return [
"firstName": "first_name",
"lastName": "last_name",
"objId": "id"
]
}
在某些项目中,你可能声明了 Realm 对象的本地属性,这些属性不应在 JSON 中发送到服务器。 你可以通过名为 realmCodableIgnoredAttributes
的类变量指定这些属性,该变量返回一个在编码时跳过的属性名称数组。
override class var realmCodableIgnoredAttributes: [String] {
return ["privateAttr1", "privateAttr2"]
}
有时,将原始 JSON 存储在属性中可能很有用。 你可以存储一个复杂的(并且可能是未知的)子树供另一个组件使用。 你可以使用 realmCodableRawJsonSubstrings
类属性指定这些属性,该属性返回一个属性名称数组,就像“忽略的属性”一样。 RealmCoder 将该键的 JSON 子树存储为该属性的字符串值。
override class var realmCodableRawJsonSubstrings: [String] {
return ["jsonSubTreeBlob"]
}
许多 REST 服务器返回包含在“外壳”中的对象以识别返回的对象类型。 例如,User
对象在 JSON 响应中可能如下所示,包含在标记为“user”的外壳中
{
"user": {
"email": "bats@superfriends.org",
"first_name": "Bruce",
"id": 3,
"last_name": "Wayne",
}
}
以同样的方式,对象数组通常具有略有不同的外壳。 User
对象列表可能看起来像,带有标记为“users”的外壳
{
"users": [
{
"email": "bats@superfriends.org",
"first_name": "Bruce",
"id": 3,
"last_name": "Wayne",
},
{
"email": "ww@superfriends.org",
...
},
{
"email": "sups@superfriends.org",
...
}
]
}
对象类可以通过类属性向 RealmCoder 表达其外壳名称。 realmObjectEnvelope
定义单个对象的外壳,realmListEnvelope
是对象列表的外壳。
对于此处的示例 JSON,你可以像这样定义外壳
override class var realmObjectEnvelope: String? {
return "user"
}
override class var realmListEnvelope: String? {
return "users"
}
结合所有这些选项,我们可以对编码/解码过程进行细粒度控制。
// Create the model
class User: Object {
@objc dynamic var objId: String = ""
@objc dynamic var username: String = ""
@objc dynamic var firstName: String = ""
@objc dynamic var lastName: String = ""
@objc dynamic var address: String = ""
@objc dynamic var phone: String = ""
@objc dynamic var configJson: String = ""
// Declare the key name for the enclosing REST wrapper for a single object
override class var realmObjectEnvelope: String? {
return "user"
}
// Declare the key name for the enclosing REST wrapper for a list of objects
override class var realmListEnvelope: String? {
return "users"
}
// Define the translation from attribute names to JSON key names:
// `first_name` is mapped to `firstName`
// `last_name` is mapped to `lastName`
// etc.
// We don't need to include username, address, or phone here because
// those attribute names match their JSON keys.
override class var realmCodableKeys: [String: String] {
return [
"firstName": "first_name",
"lastName": "last_name",
"objId": "id"
"configJson": "config_json"
]
}
// Exclude these attributes when encoding data -- don't share the
// address or phone number with anyone
override class var realmCodableIgnoredAttributes: [String] {
return ["address", "phone"]
}
// Don't parse the `configJson` subtree. That's just an opaque
// block of data we don't understand, but need to pass it to another
// object that does.
override class var realmCodableRawJsonSubstrings: [String] {
return ["configJson"]
}
}
// Encode and decode just like before. Start by creating a RealmCoder.
let coder = RealmCoder(realm: realm)
// Decode some data and commit it to the realm
let user = try coder.decode(User.self, from: jsonData)
print("Decode User with name: \(user.firstName) \(user.lastName)")
// Make some modifications to the `user` object in
// the usual Realm ways.
// Encode the object to send to the server
// Remember, the encoding excludes address and phone number
let modifiedData = try coder.encode(user)
RealmCoder 可通过 Swift Package Manager 获得。 你可以通过将此行添加到你的 Package.swift
dependencies
部分,将 RealmCoder 包含在你的项目中
.package(url: "https://github.com/OakCityLabs/RealmCoder.git", from: "1.0.0")
确保也将其添加到你的 targets
列表中
.target(name: "MyApp", dependencies: ["RealmCoder"]),
请参阅 更新日志。
RealmCoder 是 Oak City Labs 的产品。