快速映射模型的方法。
为什么需要映射?
在分层架构中,使用映射框架通常很有用,其中您通过封装对特定数据对象的更改来创建抽象层,而不是将这些对象传播到其他层(例如,外部服务数据对象、领域对象、数据传输对象、内部服务数据对象)。数据对象之间的映射传统上是通过手动编码值对象组装器(或转换器)来处理的,这些组装器在对象之间复制数据。大多数程序员都会开发某种自定义映射框架,并且花费数小时和数千行代码进行映射到和从不同的数据对象。
KeyPathMapper 是什么?
KeyPathMapper 是一个针对提供方便映射方式的轻量级库。它使用 Swift 的 KeyPath
功能来识别您要映射的属性。
使用 KeyPathMapper 的好处
考虑不需要混乱网络、持久化和领域层的情况。在您的应用程序中声明映射器,以便能够轻松高效地进行映射。
- 将映射逻辑封装到对象中,不要向模型中添加任何映射函数。
- 使用
KeyPath
功能的力量。这意味着您可以对类似\Person.friends[0].name
的内容进行映射。 - 极少的代码,只需指定 T1 的哪个 keyPath 映射到 T2 的 keyPath。
- 允许对映射值执行额外计算。
- 通过
Transformers
或 MapAddresses 的扩展来实现扩展。
使用示例
let mapper = OneWayMapper<PersonDetails, Person>()
mapper += (\PersonDetails.name).writableMapAddress <-> (\Person.firstName).readableMapAddress
mapper.register()
let person = Person(name: “John”)
// Then you can use `person => PersonDetails.self` to map to Person type to PersonDetails
let details = try? (person => PersonDetails.self)
或者您可以与 Swift 类型一起使用,例如 Arrays、Dictionaries。
let mapper = OneWayMapper<RequestOptionalResult, Dictionary<String, String>>()
mapper += (\RequestOptionalResult.value).writableMapAddress.flatMap { String($0) } <-> (\Dictionary<String, String>["key"]).writableMapAddress.flatMap { Int($0) }
var request = RequestOptionalResult(value: 0)
let dictionary = ["key": "99"]
try? mapper.update(&request, with: dictionary)
XCTAssertEqual(request.value, 99)
请查看测试以获取更多示例。
安装
CocoaPods
pod "KeyPathMapper"
Swift 包管理器
创建一个文件 Package.swift
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "YourProject",
dependencies: [
.package(url: "https://github.com/marshallxxx/KeyPathMapper.git", "2.0.0" ..< "3.0.0")
],
targets: [
.target(name: "YourProject", dependencies: ["KeyPathMapper"])
]
)
KeyPathMapper
组件
KeyPathMapper
是为可扩展性和易于使用而设计的。主要有两个基本组件
- MapAddresses - 表示您想要映射的类型的地址。
- Mappers - Mappers 由多个 MapAddresses 的组合对组成。
关于 MapAddress 的更多信息
MapAddress 表示类型中的一个地址属性,类似于一个 KeyPath
。区别在于,当您从 KeyPath
读取值时,可以通过应用转换,您可以级联多个转换以获得正确的类型。
MapAddresses 符合 MapAddressType
public protocol MapAddressType {
associatedtype RootType
associatedtype InType
associatedtype OutType
func evaluate(_ type: RootType) throws -> OutType
}
存在 3 个关联的类型
RootType
- 代表您想要映射的类型。 (例如:Person
)InType
- 代表在RootType
中的属性类型。 (例如:Person.name
)对于WritableMapAddress
这意味着您可以在 MapAddress 上设置的类型。OutType
- 代表从 MapAddress 在读取中获得的值类型。您可以通过应用不同的转换来修改此类型。这是对InType
进行一系列转换的结果。
ReadableMapAddress
ReadableMapAddress
允许只读取值。
创建 ReadableMapAddress
有两种方式
- 通过初始化:
ReadableMapAddress(keyPath: \Person.name)
- 或通过在
KeyPath
上的扩展:\Person.name.readableMapAddress
WritableMapAddress
WritableMapAddress
允许读取和写入 KeyPath。
创建 WritableMapAddress
也有两种方式
- 通过初始化:
WritableMapAddress(writableKeyPath: \Person.name)
- 或通过在
WritableKeyPath
上的扩展:\Person.name.writableMapAddress
MapAddress 操作
应用
apply
- 您可以在 MapAddress 上应用一个转换来修改 OutType
。如果您有一个非常复杂的逻辑,您可能需要一个单独的类型来包装您的 MapAddresses。为此,您创建一个符合 Transformer
的类型。
private struct RevertStringTransformer: Transformer {
init(userInfo: Void) {}
func transform(_ input: String) -> String {
return String(input.reversed())
}
}
(\Person.firstName).readableMapAddress.apply(RevertStringTransformer())
加入
join
- 允许在使用映射时使用不同的类型属性。
(\Person.firstName).readableMapAddress
.join(keyPath: \Person.lastName, { first, last in
return "\(first) \(last)"
})
映射
map
- 允许映射到不同的类型
(\Person.firstName).readableMapAddress.map { String($0.reversed()) }
OnEmpty
onEmpty
- 可在带有可选 OutType
的 MapAddresses
上使用。如果为 nil
,则会回退到默认值。
(\Request.value).readableMapAddress.onEmpty(fallback: -99)
FlatMap
flatMap
- 可在带有可选 OutType
的 MapAddresses
上使用。如果值非 nil
,则对其进行映射。
(\Request.value).readableMapAddress.flatMap({ $0 * $0 })
统计元素数量
countElements
- 可与带有 OutType
的 Collection
MapAddresses
一起使用。在集合中统计元素数量。
(\Request.elements).readableMapAddress.countElements()
映射元素
mapElements
- 可与带有 Collection
OutType
的 MapAddresses
一起使用。将集合中的元素进行映射。
(\Request.elements).readableMapAddress.mapElements { Int($0) ?? 0 }
筛选元素
filterElements
- 可以与 Collection
OutType
MapAddresses 一起使用。筛选集合中的元素。
(\Request.elements).readableMapAddress.filterElements { Int($0).flatMap { $0 > 5 } ?? false }
减少元素
reduceElements
- 可以与 Collection
OutType
MapAddresses 一起使用。将集合中的元素减少到单个值。
(\Request.elements).readableMapAddress.reduceElements("", { a, b in return "\(a)\(b)" })
排序元素
sortElements
- 可以与 Collection
OutType
MapAddresses 一起使用。对集合中的元素进行排序。
(\Request.elements).readableMapAddress.sortElements(by: { a, b in return a < b })
自定义运算符
这样你可以通过自定义转换来扩展 MapAddresses。
public extension WritableMapAddress {
func map<ResultType>(_ mapBlock: @escaping (OutType) -> (ResultType)) -> WritableMapAddress<RootType, InType, ResultType> {
return WritableMapAddress<RootType, InType, ResultType>(writableKeyPath: keyPath, transformation: { input, root in
return mapBlock(try self.evaluate(root))
})
}
}
映射器
映射器是知道如何将一种类型映射到另一种类型的类型。KeyPathMapper 提供两种映射器 OneWayMapper
和 TwoWayMapper
。
let oneWayMapper = OneWayMapper<PersonDetails, Person>()
let twoWayMapper = TwoWayMapper<PersonDetails, Person>()
它们允许您从读取类型更新写入类型的实例,在 TwoWayMapper 的情况下,它允许在两个方向上更新。例如
try? mapper.update(&person, with: personDetails)
try? (personDetails => person) // Update person from personDetails. Applicable for registered mapper.
如果类型有一个默认初始化器且符合 DefaultInitializable
,您可以立即将其转换为该类型
try? mapper.convert(value)
try? (personDetails => Person.self) // Convert personDetails to a Person instance. Applicable for registered mapper.
要向映射器添加映射,请使用
mapper += (\PersonDetails.title).writableMapAddress <-> (\Person.name).writableMapAddress
<->
运算符创建一个映射链,然后将它添加到映射器。确保对于 TwoWayMapper
您使用两个 writableMapAddress
。
OneWayMapper<ToType, FromType>
OneWayMapper
是一个单向映射器,将 FromType
映射到 ToType
。
TwoWayMapper<TypeOne, TypeTwo>
TwoWayMapper
是一个双向映射器,能够在两个方向上进行映射。
共享映射器
存在注册映射器的概念,意味着映射器将对整个应用程序可见。因此,您可以使用 =>
操作符进行转换。
let mapper = OneWayMapper<RequestResult, Request>()
mapper += (\RequestResult.value).writableMapAddress <-> (\Request.value).readableMapAddress
mapper.register()
let request = Request(value: "Test")
let result = try? (request => RequestResult.self)
以下是可以使用共享映射器执行的操作。如果没有已知映射器来处理,将抛出异常:MappingError.noRegisteredMapper
instanceA => instanceB
- 从实例A更新实例B instanceA => TypeB
- 将实例A转换为 TypeB 类型的实例 TypeA =/> TypeB
- 移除从 TypeA 转换到 TypeB 的共享映射器
KVO & KeyPathMapper
您可以使用 KeyPathMapper 观察 NSObject 的变化。每当 NSObject 实例更改与映射器注册的 keyPath 时,它将映射新值到更新实例中,从而使您能够拥有同步的实例。请查看下一个示例
let mapper = TwoWayMapper<Person, ViewMsodel>()
mapper += (\Person.name).writableMapAddress <-> (\ViewModel.title).writableMapAddress
let person = Person()
let viewModel = ViewModel()
let observation = mapper.observe(person, update: viewModel) { modifiedKeyPath in
XCTAssertEqual(\ViewModel.title, modifiedKeyPath)
}
person.name = "John"
XCTAssertEqual(viewModel.title, "John")
许可证
本项目采用 MIT 许可证条款许可。请参阅 LICENSE 文件。