当使用 Xcode 项目时
当使用 Swift Package Manager 清单时
选择 package 版本
0.1.0
main
Swift 宏,用于在将协议一致性添加到不同文件中类型的扩展时,生成代码以满足 Encodable 和 Decodable 要求。
AutoCodable
暴露了 Swift 宏,用于生成代码,以便在将 Encodable
和 Decodable
协议添加到另一个文件中类型的扩展时,满足这些协议的要求。
Swift 内置的 Codable
API 有一个主要优点 - 它在使用时会自动合成许多不同的编码和解码实现。这种行为可以轻松创建自定义类型,并使其符合 Encodable
或 Decodable
协议,而无需显式实现 func encode(to encoder: any Encoder) throws
或 init(from decoder: any Decoder) throws
。
然而,它的一个限制是必须将所有内容保存在同一个文件中。这意味着可能会发生以下情况:
// User.swift
struct User {
let firstName: String
let lastName: String
}
// User+Decodable.swift
extension User: Decodable {
// 🛑 Extension outside of file declaring struct 'User' prevents
// automatic synthesis of 'init(from:)' for protocol 'Decodable'
}
// User+Encodable.swift
extension User: Encodable {
// 🛑 Extension outside of file declaring struct 'User' prevents
// automatic synthesis of 'encode(to:)' for protocol 'Encodable'
}
可能有很多原因导致您希望将 Codable
或 Decodable
的一致性保持在包含类型声明的文件之外。不幸的是,在这种情况下,需要显式地实现它。AutoDecodable
和 AutoEncodable
宏填补了这个空白。它允许生成必要的代码,并且仍然保持声明与协议一致性的分离。
@AutoEncodable
// User.swift
struct User {
let firstName: String
let lastName: String
}
// User+Encodable.swift
@AutoEncodable
extension User: Encodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
}
}
🔽
// User+Encodable.swift
extension User: Encodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
}
}
@AutoEncodable + public 访问控制
// User.swift
public struct User {
public let firstName: String
public let lastName: String
}
// User+Encodable.swift
@AutoEncodable(accessControl: .public)
extension User: Encodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
}
}
🔽
// User+Encodable.swift
extension User: Encodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
}
public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
}
}
@AutoEncodable + singleValueContainer
// Identifier.swift
struct Identifier {
let value: Int
}
// Identifier+Encodable.swift
//❗️The name associated with `singleValue` must match the property name inside the type.
@AutoEncodable(container: .singleValue("value"))
extension Identifier: Encodable {}
🔽
// Identifier+Encodable.swift
extension Identifier: Encodable {
func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(value)
}
}
@AutoEncodable + nestedContainer
// User.swift
struct User {
let firstName: String
let lastName: String
}
// User+Encodable.swift
@AutoEncodable
extension User: Encodable {
enum CodingKeys: String, CodingKey {
case names
//❗️The nested coding keys enum must follow the name convention: `CaseName` + `CodingKeys`
enum NamesCodingKeys: String, CodingKey {
case firstName
case lastName
}
}
}
🔽
// User+Encodable.swift
extension User: Encodable {
enum CodingKeys: String, CodingKey {
case names
enum NamesCodingKeys: String, CodingKey {
case firstName
case lastName
}
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
var namesContainer = container.nestedContainer(
keyedBy: CodingKeys.NamesCodingKeys.self,
forKey: .names
)
try namesContainer.encode(firstName, forKey: .firstName)
try namesContainer.encode(lastName, forKey: .lastName)
}
}
@AutoEncodable + enum
// Membership.swift
enum Membership {
case regular
case premium
}
// Membership+Encodable.swift
@AutoEncodable(container: .singleValueForEnum)
extension Membership: Encodable {
enum CodingKeys: String, CodingKey {
case regular
case premium
}
}
🔽
// Membership+Encodable.swift
extension Membership: Encodable {
enum CodingKeys: String, CodingKey {
case regular
case premium
}
func encode(to encoder: any Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .regular:
try container.encode(CodingKeys.regular.rawValue)
case .premium:
try container.encode(CodingKeys.premium.rawValue)
}
}
}
@AutoEncodable + 属性自定义编码
// User.swift
struct User {
let firstName: String
let lastName: String
let avatarUrl: URL
}
// User+Encodable.swift
@AutoEncodable
extension User: Encodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
@EncodedValue(Avatar.self)
case avatarUrl
}
private struct Avatar: EncodableValue {
let path: String
let `extension`: String
init(from value: URL) {
self.path = value.deletingPathExtension().absoluteString
self.extension = value.pathExtension
}
}
}
🔽
// User+Encodable.swift
extension User: Encodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
@EncodedValue(Avatar.self)
case avatarUrl
}
private struct Avatar: EncodableValue {
let path: String
let `extension`: String
init(from value: URL) {
self.path = value.deletingPathExtension().absoluteString
self.extension = value.pathExtension
}
}
func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(firstName, forKey: .firstName)
try container.encode(lastName, forKey: .lastName)
try container.encode(Avatar(from: avatarUrl), forKey: .avatarUrl)
}
}
@AutoDecodable
// User.swift
struct User {
let firstName: String
let lastName: String
}
// User+Decodable.swift
@AutoDecodable
extension User: Decodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
}
}
🔽
// User+Decodable.swift
extension User: Decodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
try self.init(
firstName: container.decode(for: .firstName),
lastName: container.decode(for: .lastName)
)
}
}
@AutoDecodable + public 访问控制
// User.swift
public struct User {
public let firstName: String
public let lastName: String
}
// User+Decodable.swift
@AutoDecodable(accessControl: .public)
extension User: Decodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
}
}
🔽
// User+Decodable.swift
extension User: Decodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
}
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
try self.init(
firstName: container.decode(for: .firstName),
lastName: container.decode(for: .lastName)
)
}
}
@AutoDecodable + singleValueContainer
// Identifier.swift
struct Identifier {
let value: Int
}
// Identifier+Decodable.swift
//❗️The name associated with `singleValue` must match the property name inside the type.
@AutoDecodable(container: .singleValue("value"))
extension Identifier: Encodable {}
🔽
// Identifier+Decodable.swift
extension Identifier: Decodable {
init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
try self.init(value: container.decode())
}
}
@AutoDecodable + nestedContainer
// User.swift
struct User {
let firstName: String
let lastName: String
}
// User+Decodable.swift
@AutoDecodable
extension User: Decodable {
enum CodingKeys: String, CodingKey {
case names
//❗️The nested coding keys enum must follow the name convention: `CaseName` + `CodingKeys`
enum NamesCodingKeys: String, CodingKey {
case firstName
case lastName
}
}
}
🔽
// User+Decodable.swift
extension User: Decodable {
enum CodingKeys: String, CodingKey {
case names
enum NamesCodingKeys: String, CodingKey {
case firstName
case lastName
}
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let namesContainer = try container.nestedContainer(
keyedBy: CodingKeys.NamesCodingKeys.self,
forKey: .names
)
try self.init(
firstName: namesContainer.decode(for: .firstName),
lastName: namesContainer.decode(for: .lastName)
)
}
}
@AutoDecodable + enum
// Membership.swift
enum Membership {
case regular
case premium
}
// Membership+Decodable.swift
@AutoDecodable(container: .singleValueForEnum)
extension Membership: Decodable {
enum CodingKeys: String, CodingKey {
case regular
case premium
}
}
🔽
// Membership+Decodable.swift
extension Membership: Decodable {
enum CodingKeys: String, CodingKey {
case regular
case premium
}
init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
let stringValue = try container.decode(String.self)
switch stringValue {
case CodingKeys.regular.rawValue:
self = .regular
case CodingKeys.premium.rawValue:
self = .premium
default:
throw DecodingError.dataCorruptedError(
in: container,
debugDescription: "Invalid value: \(stringValue)"
)
}
}
}
@AutoDecodable + 属性自定义解码
// User.swift
struct User {
let firstName: String
let lastName: String
let avatarUrl: URL?
}
// User+Decodable.swift
@AutoDecodable
extension User: Decodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
@DecodedValue(Avatar.self)
case avatarUrl
}
private struct Avatar: DecodableValue {
let path: String
let `extension`: String
func value() -> URL? {
.init(string: path + "." + `extension`)
}
}
}
🔽
// User+Decodable.swift
extension User: Decodable {
enum CodingKeys: String, CodingKey {
case firstName
case lastName
@DecodedValue(Avatar.self)
case avatarUrl
}
private struct Avatar: DecodableValue {
let path: String
let `extension`: String
func value() -> URL? {
.init(string: path + "." + `extension`)
}
}
init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
try self.init(
firstName: container.decode(for: .firstName),
lastName: container.decode(for: .lastName),
avatarUrl: container.decode(Avatar.self, forKey: .avatarUrl).value()
)
}
}
AutoCodable
在 MIT 许可证下发布。有关更多信息,请参见 LICENSE 文件。