Swift Package Index 标志。Swift Package Index

跟踪 Swift 6 严格并发检查在数据竞争安全性方面的采用情况。有多少软件包为 Swift 6 做好准备

当使用 Xcode 项目时

当使用 Swift Package Manager 清单时

选择 package 版本

0.1.0

main


Swift 宏,用于在将协议一致性添加到不同文件中类型的扩展时,生成代码以满足 Encodable 和 Decodable 要求。




AutoCodable

AutoCodable 暴露了 Swift 宏,用于生成代码,以便在将 EncodableDecodable 协议添加到另一个文件中类型的扩展时,满足这些协议的要求。

动机

Swift 内置的 Codable API 有一个主要优点 - 它在使用时会自动合成许多不同的编码和解码实现。这种行为可以轻松创建自定义类型,并使其符合 EncodableDecodable 协议,而无需显式实现 func encode(to encoder: any Encoder) throwsinit(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'
}

可能有很多原因导致您希望将 CodableDecodable 的一致性保持在包含类型声明的文件之外。不幸的是,在这种情况下,需要显式地实现它。AutoDecodableAutoEncodable 宏填补了这个空白。它允许生成必要的代码,并且仍然保持声明与协议一致性的分离。

用法

Encodable

@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)
    }
}

Decodable

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