一个生成 DTO 的 Swift 宏。
DecodableFromDTOProtocol
,使 DTO 能够轻松封装数据@DecodableFromDTO
,用于代码生成@ConvertDTOType(from: SourceType, to: BusinessDataType, convert: ConversionClosure)
,提供有关如何将数据从一种类型转换为另一种类型的信息@ConvertFromDTO
,它是前一个属性的缩短版本,适用于从 DTO 生成的属性@DTOProperty(name: "a_name")
,用于更改 DTO 属性的名称以适应接收到的数据将以下内容添加到您的 Package.swift
文件中
dependencies: [
.package(url: "https://github.com/OctoPoulpeStudio/DTOMacro.git", from: "1.0.0")
]
import DTOMacro
@DecodableFromDTO
添加到您的业务数据的结构体名称中@DecodableFromDTO
struct MyData {
let name: String
let birthday:Date?
}
3.1. @ConvertDTOType(from: SourceType, to: BusinessDataType, convert: ConversionClosure)
- SourceType: 您收到的数据的类型 - BusinessType: 您要转换的属性的类型 - ConversionClosure: 用于将 SourceType 转换为 BusinessType 的显式闭包。 - > [!NOTE]: 闭包的主体必须位于属性中,因为它会被 SwiftSyntax 复制为字符串。
@DecodableFromDTO
struct MyData {
let name: String
@ConvertDTOType(from: String, to: Date, convert: {ISO8601DateFormatter().date(from:$0)})
let birthday:Date?
}
3.2. 使用 @ConvertFromDTO
自动转换从 DTO 创建的对象
@DecodableFromDTO
struct MyData {
let name: String
@ConvertFromDTO
let myProperty:ATypeUsingTheDecodableFromDTOProtocol
}
示例
@DecodableFromDTO
struct MyData {
let name: String
@ConvertDTOType(from: String, to: Date, convert: {ISO8601DateFormatter().date(from:$0)})
let birthday:Date?
}
将生成
extension MyData: DecodableFromDTOProtocol {
public struct DTO: Decodable {
public let name: String
public let birthdate: String
}
private struct DTOConversionProcessor {
fileprivate static var birthdate: (String) -> Date? = {
ISO8601DateFormatter().date(from: $0)
}
}
public init(from dto: DTO) {
self.name = dto.name
self.birthdate = DTOConversionProcessor.birthdate(dto.birthdate)
}
}
@DTOProperty(name: "a_name")
属性来更改 DTO 对象中属性的名称,以反映您接收到的名称,类似于使用 CodingKeys
。像这样
@DecodableFromDTO
struct MyData {
@DTOProperty(name: "a_name")
let name: String
@DTOProperty(name: "date_of_birth")
@ConvertDTOType(from: String, to: Date?, convert: { ISO8601DateFormatter().date(from:$0)})
let birthdate: Date?
}
它将展开为
extension MyData: DecodableFromDTOProtocol {
public struct DTO: Decodable {
public let a_name: String
public let date_of_birth: String
}
private struct DTOConversionProcessor {
fileprivate static var date_of_birth: (String) -> Date? = {
ISO8601DateFormatter().date(from: $0)
}
}
public init(from dto: DTO) {
self.name = dto.a_name
self.birthdate = DTOConversionProcessor.date_of_birth(dto.date_of_birth)
}
}
您可以将 access
参数传递给 @DecodableFromDTO
属性,以定义 DTO 属性的可访问性,如下所示
@DecodableFromDTO(access: .internal)
struct MyData {...
注意
private 访问器不可用,否则无法将 DTO 中的数据传输到主类型。
默认情况下,访问器是 public
。
DTO 代表数据传输对象 (Data Transfer Object)。这是一种设计模式,允许业务数据与应用程序的需求保持一致。
通过在您的语言(本例中为 Swift)中创建一个中间数据类型,该数据类型表示应用程序接收到的数据结构(通常是通过 JSON 从 API 接收)。
例如,您可能会收到来自服务器的名为“birthdate”的数据,它代表一个日期,但实际上是一个字符串。
如何无缝地将字符串转换为日期?您可以使用一个特殊的 init(from:Decoder)
初始化器,其中包含您忘记如何编写的特定代码,或者您可以使用 DTO 通过这个中间对象轻松转换数据。
这将看起来像这样
{
birthdate:"2023-07-07T17:06:40.0433333+02:00"
}
struct MyData {
let birthday:Date?
}
在这里,您无法直接将 String
转换为 Date?
。
因此,您可以使用 DTO 设计模式
struct MyDataDTO {
let birthdate: String
}
并在您的业务数据类型中创建一个特殊的 init
struct MyData {
let birthday:Date?
init(from dto: MyDataDTO) {
self.birthdate = ISO8601DateFormatter().date(from:dto.birthday)
}
}
现在,使用 DTO 的更好方法是将 DTO 类型嵌套到您的业务数据中,从而以统一的方式编写它们,我们可以将它们嵌入到协议 DecodableFromDTOProtocol
中。同时,我们可以将它们分组在一个扩展中,以保持我们的业务数据干净
public protocol DecodableFromDTOProtocol {
associatedtype DTO: Decodable
init(from dto: DTO) throws
}
struct MyData {
let birthday:Date?
}
extension MyData: DecodableFromDTOProtocol {
public struct DTO {
let birthday: String
}
init(from dto: DTO) {
self.birthday = ISO8601DateFormatter().date(from:dto.birthday)
}
}
我们拥有的代码易于阅读,但不太容易编写。正如您所看到的,它添加了大量的样板代码。现在我们有一个名为 DTO 的嵌套结构体,它具有与我们主结构体相同的属性,并且我们有一个新的初始化器来执行转换。
这意味着如果发生任何更改,我们需要注意 3 个地方。
SwiftMacro 是 Swift 5.9 的一项功能,它允许我们开发者创建在编译时生成代码的代码。
那么如何使用 SwiftMacro 来拯救我们呢?
我们的想法是创建一个执行 DecodableFromDTO
所有工作的 attached macro
。 并使用其他不生成任何代码的 attached macro 作为一种通过滥用宏系统来标记属性的方式来生成我们自己的属性。
因此,DecodableFromDTO
是获取任何代码生成的强制性步骤。 其他属性 @ConvertDTOType(from: APIType, to: BusinessType, convert: ConversionClosure)
和 @ConvertFromDTO
只是为 DecodableFromDTO
提供上下文,但不生成任何内容。
[!NOTE]
- 通过使用宏系统,我们可以传递参数并进行类型检查。
- 这些宏还用于检测其使用中的错误。
DecodableFromDTO
宏的工作方式如下
DTO
的新类型DTO
。init(from dto: DTO)
此包嵌入了 DecodableFromDTOProtocol
协议。 它还嵌入了 JsonDecoder 的一个扩展,以直接和无缝地从 JSON 获取业务数据
extension JSONDecoder {
func decode<T>(_ type: T.Type, from data: Data) throws -> T where T: DecodableFromDTOProtocol {
try T(from: decode(T.DTO.self, from: data))
}
}
[!NOTE]:该协议和扩展是由 Luis Recuenco 创建的,并取自他在 Better Programming 上的文章。