KeyPathMapper 2.0.1

KeyPathMapper 2.0.1

Evghenii Nicolaev 维护。



KeyPathMapper

快速映射模型的方法。

Build Status platforms pod Swift Package Manager compatible

为什么需要映射?

源自 dozer 框架

在分层架构中,使用映射框架通常很有用,其中您通过封装对特定数据对象的更改来创建抽象层,而不是将这些对象传播到其他层(例如,外部服务数据对象、领域对象、数据传输对象、内部服务数据对象)。数据对象之间的映射传统上是通过手动编码值对象组装器(或转换器)来处理的,这些组装器在对象之间复制数据。大多数程序员都会开发某种自定义映射框架,并且花费数小时和数千行代码进行映射到和从不同的数据对象。

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 - 可在带有可选 OutTypeMapAddresses 上使用。如果为 nil,则会回退到默认值。

(\Request.value).readableMapAddress.onEmpty(fallback: -99)

FlatMap

flatMap - 可在带有可选 OutTypeMapAddresses 上使用。如果值非 nil,则对其进行映射。

(\Request.value).readableMapAddress.flatMap({ $0 * $0 })

统计元素数量

countElements - 可与带有 OutTypeCollection MapAddresses 一起使用。在集合中统计元素数量。

(\Request.elements).readableMapAddress.countElements()

映射元素

mapElements - 可与带有 Collection OutTypeMapAddresses 一起使用。将集合中的元素进行映射。

(\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 提供两种映射器 OneWayMapperTwoWayMapper

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 文件。