DataMapper
介绍
DataMapper是一个框架,用于在不同的数据表示标准(目前我们支持JSON,但其他也可以轻松添加)之间安全地序列化和反序列化对象。
其优点包括:
- 易于使用的API
- 编译时安全性(尽可能的实现)
- 支持自定义序列化器(允许您通过实现一个类来简单地更改数据表示格式)
- 多态
- 线程安全(取决于您的使用情况)
- 支持单向使用(如果您不需要另一个方向,您不需要实现它)
变更日志
所有更改和新功能列表可以在此处找到。
要求
- Swift 4
- iOS 8+
安装
CocoaPods
数据映射器可以通过CocoaPods获得。要安装它,只需将以下行添加到Podfile中的测试目标。
pod "DataMapper"
这将自动包含每个子规范(核心和所有序列化器)。
如果您想要不带序列化器的数据映射器(您有自己实现的),请使用
pod "DataMapper/Core"
序列化器的每个实现都有自己的子规范。例如
pod "DataMapper/JsonSerializer"
这些子规范以核心为依赖项,因此无需显式指定它。
使用方法
以下是此库提供的所有功能和如何使用它们。一些使用示例可以在测试中找到。
使用的术语
- 映射 - 反序列化或序列化
- 映射协议 -
Deserializable
、Serializable
或Mappable
快速概述
/*
[{
"number": 1,
"text": "A"
}, {
"number": 2,
"text": "B"
}]
*/
let inputData: NSData = ... // Some data in JSON to be deserialized.
let objectMapper = ObjectMapper()
let serializer = JsonSerializer()
// Deserialization
let type = serializer.deserialize(inputData)
let objects: [MyObject]? = objectMapper.deserialize(type)
... // Do some stuff with objects.
// Serialization
let changedType = objectMapper.serialize(objects)
let outputData = serializer.serialize(changedType)
// Can be deserilized and serialized.
struct MyObject: Mappable {
var number: Int?
var text: String?
init(_ data: DeserializableData) throws {
try mapping(data)
}
mutating func mapping(_ data: inout MappableData) throws {
data["number"].map(&number)
data["text"].map(&text)
}
}
// Can be only deserialized.
struct MyDeserializableObject: Deserializable {
let number: Int?
let text: String?
init(_ data: DeserializableData) throws {
number = data["number"].get()
text = data["text"].get()
}
}
// Can be only serialized.
struct MySerializableObject: Serializable {
let number: Int?
let text: String?
init(number: Int?, text: String?) {
self.number = number
self.text = text
}
func serialize(to data: inout SerializableData) {
data["number"].set(number)
data["text"].set(text)
}
}
支持的类型
SupportedType
在ObjectMapper和序列化器之间创建了一个中间级别。它是一个像枚举的结构(由于性能原因使用类实现),代表基本数据类型(null
、string
、bool
、int
、double
、array
、dictionary
)。每种类型都有一个关联的属性,如果它是正确的类型,则返回其值,否则返回nil。对于null
有一个小小的例外,其属性名为isNull
,并返回Bool
。
extension SupportedType {
var isNull: Bool
var string: String?
var bool: Bool?
var int: Int?
var double: Double?
var array: [SupportedType]?
var dictionary: [String: SupportedType]?
mutating func addToDictionary(key: String, value: SupportedType)
}
例如
let type: SupportedType = .string("A")
type.string // "A"
type.number // nil
addToDictionary
将键值对添加到字典中。如果当前类型不是字典,则将其替换为一个新的字典。
SupportedType
可以使用以下静态方法创建
extension SupportedType {
static var null: SupportedType
static func string(_ value: String) -> SupportedType
static func bool(_ value: Bool) -> SupportedType
static func int(_ value: Int) -> SupportedType
static func double(_ value: Double) -> SupportedType
static func array(_ value: [SupportedType]) -> SupportedType
static func dictionary(_ value: [String: SupportedType]) -> SupportedType
static func intOrDouble(_ value: Int) -> SupportedType
}
intOrDouble
解决了在文本表示中数字模糊的问题。例如:1
是 Int
还是 Double
。如果使用 intOrDouble(1)
创建 SupportedType
,则 .int
和 .double
都返回 1
。相反,如果你使用 .int(1)
,只有 .int
返回 1
,而 .double
返回 nil
。
ObjectMapper
final class ObjectMapper {
func serialize<T: Serializable>(_ value: T?) -> SupportedType
func deserialize<T: Deserializable>(_ type: SupportedType) -> T?
// + other overloads for supported types mentioned below
}
ObjectMapper
将对象映射到 SupportedType
。它有两种类型的方法
serialize
- 接受 Swift 对象并将它们转换为SupportedType
。deserialize
- 接受SupportedType
并将它们转换为 Swift 对象。
支持的 Swift 类型
T?
[T]?
[String: T]?
[T?]?
[String: T?]?
其中 T
符合 Map 协议(取决于方法)。如果 T
不符合此协议,则需要将 Transformation
实例作为名为 using
的第二个参数传递。
如你所见,deserialize
总是返回可选类型。如果 SupportedType
是 .null
或无法转换为 T
,则返回 nil
。
serialize
接受可选和非可选类型。如果传递 nil
,则结果 SupportedType
是 .null
。
[T]?
与 [T?]?
在反序列化中有所不同。如果数组中的某个元素是 nil
(SupportedType
是 .null
或对象无法反序列化),则丢弃一切并返回 nil
。在 [T?]?
的情况下,将添加 nil
值到数组中。同样适用于字典。
Serializer
protocol Serializer {
func serialize(_ supportedType: SupportedType) -> Data
func deserialize(_ data: Data) -> SupportedType
}
Serializer
表示将 SupportedType
映射到 NSData
的某个对象。您无需实现 Serializer
就可以将 SupportedType
映射到 NSData
,但是建议这样做,因为这样对象可以在其他库中使用。(此协议仅提供标准化 API。)
有时(几乎总是)与 String
一起工作比与 Data
一起工作更简单。因此,我们在 Serializer
中添加了扩展方法
extension Serializer {
func serialize(toString supportedType: SupportedType) -> String
func deserialize(fromString string: String) -> SupportedType
}
注意:String
使用 UTF-8 编码转换为 Data
(并返回)。
TypedSerializer
protocol TypedSerializer: Serializer {
associatedtype DataType
func typedSerialize(_ supportedType: SupportedType) -> DataType
func typedDeserialize(_ data: DataType) -> SupportedType
}
使用泛型和方法扩展 Serializer
。有时,您可能无法以 NSData
,而是以 JSON(《Any》带有特定结构)的形式从其他库中获取数据,而这些数据的转换并不利于性能。
#### 预实现序列化器
JsonSerializer
正如其名所述,它支持与JSON一起使用。它符合 TypedSerializer
协议,而且 DataType
是 Any
。对数据格式(《Any 或 NSJSONSerialization
相同。
Map 协议
可反序列化
protocol Deserializable {
init(_ data: DeserializableData) throws
}
允许符合该协议的对象使用 ObjectMapper
从 SupportedType
反序列化。在这个 init
中,您需要使用 DeserializableData
(参见 DeserializableData)初始化对象。如果由于某种原因无法创建对象(数据错误),则抛出 DeserializationError
。
可序列化
protocol Serializable {
func serialize(to data: inout SerializableData)
}
允许遵守该协议的对象使用 ObjectMapper
将其序列化为 SupportedType
。在 serialize
中将您想要序列化的数据(不必要包含所有数据)设置为 SerializableData
(参见 SerializableData)。
警告:如果序列化的数据是可变的,则此方法可能导致线程不安全(不可变性和结构是这里的帮手)。
Mappable 协议
protocol Mappable: Serializable, Deserializable {
mutating func mapping(_ data: inout MappableData) throws
}
Mappable
协议结合了 Deserializable
和 Serializable
。它为 serialize
提供默认实现,但需要手动实现 init
,通常如下所示:
struct SomeObject: Mappable {
init(_ data: DeserializableData) throws {
try mapping(data)
}
...
这也意味着在调用 mapping
方法之前必须初始化对象。
如果您更改了 init
或 serialize
的默认实现,不要忘记在 init
中调用 try mapping(data)
或在 serialize
中调用 mapping(&data)
。
在 mapping
中,您有 MappableData
(参见 MappableData)的访问权限,允许您指定如何将对象映射到一个地方。为此,字段必须是可变的。不可变字段需要单独在 init
和 serialize
中定义,如下所示:
struct SomeObject: Mappable {
let constant: Int?
var variable: Int?
init(_ data: DeserializableData) throws {
constant = data["constant"].get()
try mapping(data)
}
func serialize(to data: inout SerializableData) {
data["constant"].set(constant)
mapping(&data)
}
mutating func mapping(_ data: inout MappableData) throws {
data["variable"].map(&variable)
}
}
抛出操作与 Deserializable
中相同。
警告:与 Serializable
相同的线程安全问题。
可序列化数据/序列化数据/可映射数据
它们在映射协议中对应的相应方法中使用。它们为特定的方法提供了一个许多重载和一个索引。这个索引用作字典中的键,它可以像这样嵌套
data["a"]["b"]
data[["a", "b"]]
data["a", "b"]
这些全部意味着数据对应于一个键为 "a" 的字典,该字典的键为 "b"。
该方法具有与 ObjectMapper 相同类型的重载以及相同的操作行为(参见 ObjectMapper),并且对于每个重载,有三个选项
- 与 ObjectMapper 相同 - 与可选类型一起工作,nil 表示 .null
try
- 与非可选类型一起工作,如果找到 .null 则抛出异常or
- 与非可选类型一起工作,用 or 的值替换 .null
可序列化数据
DeserializableData
在 Deserializable
的 init
中使用。方法名为 get
,从数据中检索值。使用方法
let value: Int? = data["value"].get()
let value: Int = try data["value"].get()
let value: Int = data["value"].get(or: 0)
let value: X? = data["value"].get(using: XTransformation())
let value: X = try data["value"].get(using: XTransformation())
let value: X = data["value"].get(using: XTransformation(), or: X())
序列化数据
SerializableData
在 Serializable
的 serialize
中使用。方法名为 set
,将值设置到数据中。使用方法
data["value"].set(value)
data["value"].set(value, using: XTransformation())
注意:由于 set
可以接受可选和非可选类型,所以没有为 try
和 or
提供重载(没有必要,因为它们可以接受这两种类型)。
可映射数据
MappableData
在 Mappable
的 mapping
中使用。该方法名为 map
,它根据上下文的行为类似于 get
或 set
。使用方法
data["value"].map(&value) // var value: Int?
try data["value"].map(&value) // var value: Int
data["value"].map(&value, or: 0) // var value: Int
data["value"].map(&value, using: XTransformation()) // var value: X?
try data["value"].map(&value, using: XTransformation()) // var value: X
data["value"].map(&value, using: XTransformation(), or: X()) // var value: X
注意:只有在反序列化过程中,try
和 or
才会影响 map
的结果。
转换
转换提供了一种指定对象如何映射的另一种方式。它们用于覆盖Map协议中方法的实现,或者允许映射不符合Map协议的类型。
它们有三种类型:仅用于反序列化的DeserializableTransformation
,仅用于序列化的SerializableTransformation
,以及两者都有的Transformation
。所有这些特定的实现(如AnyTransformation
、SupportedTypeConvertible
等)都三个版本,名称相应。
了解如何创建新转换的最佳方式是查看现有代码。
预实现的转换
EnumTransformation
- 使用RawRepresentable
URLTransformation
-String
到NSURL
(在init
中可以指定使用相对或绝对路径)
日期类型
CustomDateFormatTransformation
- 使用init
中指定的formatString作为NSDateFormatter.dateFormat
DateFormatterTransformation
- 使用NSDateFormatter
DateTransformation
-Double
作为timeIntervalSince1970ISO8601DateTransformation
- ISO8601格式的String
值类型
BoolTransformation
DoubleTransformation
IntTranformation
StringTransformation
AnyTransformation
AnyTransformation
表示Swift协议中使用关联类型作为变量类型的模式。要将任何Transformation
实例转换为它,只需调用transformation.typeErased()
。这在下面提到的Transformation
的特定实现中经常需要。
注意:AnyTransformation
有仅反序列化或仅序列化的变体,它们也有typeErased()
方法。所以有时可能需要显式指定此方法的输出类型。例如
let transformation = IntTransformation()
let anyTransformation: AnyTransformation = transformation.typeErased()
let anyDeserializableTransformation: AnyDeserializableTransformation = transformation.typeErased()
let anySerializableTransformation: AnySerializableTransformation = transformation.typeErased()
SupportedTypeConvertible
通过SupportedTypeConvertible
扩展类型提供Map协议的默认实现,如果该类型已经有转换。所有具有转换的值类型和NSURL
都符合该协议。
以下是Int
的一个示例实现
extension Int: SupportedTypeConvertible {
static var defaultTransformation = IntTransformation().typeErased()
}
注意:这允许您直接在ObjectMapper
中使用像Int
这样的类型,而无需传递转换。
复合转换
复合转换
允许您重用已存在的转换
,以转换类型_transitiveObject
到/自SupportedType
。然后,您只需编写将_transitiveObject
转换为/自Object
的代码。
委托转换
委托转换
与复合转换
相似,因为它使用另一个转换
,但在此之后没有其他转换。通常,这用于专门化更通用的转换
。例如,这是ISO8601DateTransformation
的实现。
struct ISO8601DateTransformation: DelegatedTransformation {
typealias Object = Date
let transformationDelegate = CustomDateFormatTransformation(formatString: "yyyy-MM-dd'T'HH:mm:ssZZZZZ").typeErased()
}
因为已经有了处理转换为/自.string
的CustomDateFormatTransformation
,因此在这里重新实现它是不必要的。只需指定使用的格式即可。
多态
protocol Polymorph {
/// Returns type to which the supportedType should be deserialized.
func polymorphType<T>(for type: T.Type, in supportedType: SupportedType) -> T.Type
/// Write info about the type to supportedType if necessary.
func writeTypeInfo<T>(to supportedType: inout SupportedType, of type: T.Type)
}
多态
表示一个可以决定在运行时应将数据反序列化到哪个对象的对象,并在序列化到SupportedType
时保留有关对象具体类型的元数据。
要使用多态
,请用它与ObjectMapper
进行初始化。例如:
let objectMapper = ObjectMapper(polymorph: StaticPolymorph())
目前只有一个实现(StaticPolymorph
),但是一旦Swift添加了反射,我们将实现新的一个(动态)。也欢迎您实现自己的多态性,如果我们的多态性不足以满足您的需求,您甚至可以选择“硬编码”应使用哪些类型。
以下是多态可以做什么的示例
class A: Mappable {
let value: Int?
...
}
class B: A {
let text: String?
...
}
struct MyPolymorph: Polymorph {
// If B is castable to T and supportedType contains a dictionary with key "type" and the value "B", then the type to use is `B`, otherwise does nothing.
func polymorphType<T>(for type: T.Type, in supportedType: SupportedType) -> T.Type {
if let bType = B.self as? T.Type, supportedType.dictionary?["type"]?.string == "B" {
return bType
}
return type
}
// If T is B, write info about it into supportedType.
func writeTypeInfo<T>(to supportedType: inout SupportedType, of type: T.Type) {
if type == B.self {
supportedType.addToDictionary(key: "type", value: .string("B"))
}
}
}
let objectMapper = ObjectMapper()
let objectMapperWithPolymorh = ObjectMapper(polymorph: MyPolymorph())
let aType: SupportedType = .dictionary(["value": .int(1)])
let bType: SupportedType = .dictionary(["value": .int(2), "text": .string("text"), "type": .string("B")])
// Deserialization
let aObject: A? = objectMapper.deserialize(aType) // A(value: 1) - no surprise here
let bObject: A? = objectMapper.deserialize(bType) // A(value: 2) - the rest of the dictionary is ignored
let aPolymorphic: A? = objectMapperWithPolymorh.deserialize(aType) // A(value: 1) - again the same result
let bPolymorphic: A? = objectMapperWithPolymorh.deserialize(bType) // B(value: 2, text: "text") - this time the polymorph comes into play
// Serialization
objectMapper.serialize(aObject) // .dictionary(["value": .int(1)])
objectMapper.serialize(bObject) // .dictionary(["value": .int(2)])
objectMapperWithPolymorh.serialize(aPolymorphic) // .dictionary(["value": .int(1)]) - so far no difference
objectMapper.serialize(bPolymorphic) // .dictionary(["value": .int(2), "text": .string("text")])
objectMapperWithPolymorh.serialize(bPolymorphic) // .dictionary(["value": .int(2), "text": .string("text"), "type": .string("B")]) - type is added
静态多态
静态多态
通过在supportedType
中寻找具有特定键(用于确定键的键是由输入对象类型确定的)的字典条目来解决类型。然后,将该键的值与已知类型的名称进行比较。如果找到匹配项,则返回正确的类型,否则返回输入类型。当序列化时,StaticPolymorh
将对应的序列化类型的多项式键值对添加到SupportedType
中。
StaticPolymorph
只影响实现了 Polymorphic
协议的对象。对于其他类型,polymorphType
返回输入类型,而 writeTypeInfo
不执行任何操作。
注意:仅实现 Polymorphic
协议不足以使对象在 ObjectMapper
中使用。为了解决这个问题,存在类型别名,它们将 Polymorphic
与 Map 协议组合:PolymorphicDeserializable
、PolymorphicSerializable
和 PolymorphicMappable
。
注意:StaticPolymorph
的限制是只能使用类。无法使用协议和结构体。
多态
Polymorphic
的定义如下
protocol Polymorphic: AnyObject {
static var polymorphicKey: String { get }
static var polymorphicInfo: PolymorphicInfo { get }
}
polymorphicKey
代表上述提到的键。 (在哪里查找类型的名称。) 可以覆盖 polymorphicKey
。这允许每种类型都可以与键和名称的组合进行识别。没有定义如果有多个键在 SupportedType
中与有效名称同时存在会发生什么!只要组合是唯一的,就可以有多个具有相同键或名称的子类型。
polymorphicInfo
定义了类型名称及其子类型(它们不必是直接子类型)。无法检查这些类型是否真的是子类型,但如果使用 GenericPolymorphicInfo
,这将进行检查,因此不会有问题。如果注册的子类型不是真正的子类型,则 StaticPolymorph
将忽略它(但不要依赖于这种行为)。当 StaticPolymorph
解决子类型时,它仅依赖于 polymorphicInfo
提供的信息,因此对于它来说,没有在输入类型(或在注册的子类型)中注册的子类型不存在。为了防止潜在的误用,禁止将没有覆盖 polymorphicInfo
的 Polymorphic
用作输入类型(如下例所示)。
Polymorphic
提供了一个 createPolymorphicInfo()
方法,该方法返回 GenericPolymorphicInfo
。此方法具有可选参数 name
,表示类型的多态名称(默认值是类型的真实名称)。GenericPolymorphicInfo
允许您使用 register()
和 with()
覆载注册子类型 (with()
返回 self
以允许链接)。
示例
class A: Polymorphic {
class var polymorphicKey: String {
return "K"
}
class var polymorphicInfo: PolymorphicInfo {
return createPolymorphicInfo(name: "Base").with(subtypes: B.self, D.self)
}
}
class B: A {
override class var polymorphicInfo: PolymorphicInfo {
return createPolymorphicInfo().with(subtype: C.self)
}
}
class C: B {
override class var polymorphicKey: String {
return "C"
}
}
class D: C {
override class var polymorphicInfo: PolymorphicInfo {
return createPolymorphicInfo()
}
}
注意:此示例跳过了 Map 协议的实现。
有一些需要注意的事情。
C
覆盖了polymorphicKey
,这意味着:A
和B
的键是 "K",而C
和D
的键是 "C"。因此SupportedType.dictionary(["C": .string("C")]
代表C
,而SupportedType.dictionary(["K": .string("C")]
在这种上下文中没有意义。A
有显式的名称 "Base"。因此SupportedType.dictionary(["K": .string("Base")]
代表A
。C
没有覆盖polymorphicInfo
。这意味着不能将C
用作输入类型(将引发异常),而尽管它永远不会解析为其他类型,但D
可以使用。D
在A
中而不是在B
中注册。因此,由于B
不了解D
,如果B
是输入类型,则永远无法获取D
作为子类型。A
了解C
,因为C
在B
中注册,而B
在A
中注册。
线程安全
DataMapper 旨在在后台线程中使用(默认实现是线程安全的)。如果您这样使用它,请确保 Map 协议中所有方法的实现都是线程安全的(或者您所使用的对象不能同时在两个线程中使用)。您自己实现的类似 Serializer
、Polymorph
、Transformation
等协议的实现在线程安全方面也必须保证。
版本控制
该库使用语义版本控制。在 1.0 版本之前,API 可能即使在次要版本中也可能出现破坏性更改。我们考虑 0.1 版本为预发布版本,这意味着 API 应该是稳定的,但在实际项目中尚未经过测试。在此测试之后,我们进行必要的调整,并将版本提升到 1.0(首次发布)。
作者
- Tadeas Kriz,[email protected]
- Filip Dolník,[email protected]
测试中使用的库
许可证
DataMapper 在 MIT 许可证 下可用。