Swift 如何反映可编码/可编码密钥协议的设计?

Swift 如何反映可编码/可编码密钥协议的设计?,swift,enums,protocols,Swift,Enums,Protocols,我试图实现类似于Swift如何在实现Codable的类中定义的枚举上使用CodableKeys协议集的功能。在我的例子中,类是CommandHandler,枚举是CommandIds,它不需要编译器的代码生成器,因为枚举总是显式指定的 这是我想要的东西的简化版本 protocol CommandId{} protocol CommandHandler{ associatedtype CommandIds : CommandId, RawRepresentable } class Han

我试图实现类似于Swift如何在实现
Codable
的类中定义的枚举上使用
CodableKeys
协议集的功能。在我的例子中,类是
CommandHandler
,枚举是
CommandIds
,它不需要编译器的代码生成器,因为枚举总是显式指定的

这是我想要的东西的简化版本

protocol CommandId{}
protocol CommandHandler{
    associatedtype CommandIds : CommandId, RawRepresentable
}

class HandlerA : CommandHandler{
    enum CommandIds : String, CommandId{
        case commandA1
        case commandA2
    }
}

class HandlerB : CommandHandler{
    enum CommandIds : String, CommandId{
        case commandB1
        case commandB2
        case commandB3
    }
}

func processHandler<T:CommandHandler>(_ handler:T){
    // Logic to iterate over CommandIds. <-- This is where I get stumped
}

let handlerA = HandlerA()
processHandler(handlerA)
protocolcommandid{}
协议命令处理程序{
associatedtype CommandId:CommandId,可表示
}
类HandlerA:CommandHandler{
枚举CommandId:字符串,CommandId{
案例命令1
案例命令2
}
}
类HandlerB:CommandHandler{
枚举CommandId:字符串,CommandId{
案例命令B1
案例命令B2
案例3
}
}
func processHandler(handler:T){

//迭代CommandID的逻辑。我想到的唯一解决方案是使用
associatedtype
并将枚举移到协议之外,执行如下操作:

enum Commands:String {
    case default_command = ""
}

protocol CommandDef {
    associatedtype Commands
}

class MyClassA : CommandDef {
    enum Commands : String {
        case commandA1
        case commandA2 = "Explicit A2"
    }
}

class MyClassB : CommandDef {
    enum Commands : String {
        case commandB1
        case commandB2 = "Explicit B2"
        case commandB3
    }
}

swift中不允许在协议中使用枚举。如果可能的话,协议将不允许引用枚举的案例。您必须强制转换,最终打破协议理想

也许关联类型最适合你的目的

enum Commands:String {
    case compliaceType = ""
}

protocol CommandDef {
    associatedtype Commands
}

class MyClassA : CommandDef {

    enum Commands : String {
        case commandA1 = "hi"
        case commandA2 = "Explicit A2"
    }


}

class MyClassB : CommandDef {
    enum Commands : String {
        case commandB2 = "Explicit B2"
    }

}

print(MyClassA.Commands.commandA1)

Swift语言本身没有办法模仿Codable,因为Codable的实现依赖于生成特殊情况代码的编译器。具体来说,没有创建默认CodingKeys枚举的协议扩展,除非您指定你自己动手吧

这类似于Swift编译器如何自动为结构创建初始值设定项(“成员初始值设定项”)除非您指定自己的初始值设定项。在这种情况下,也没有协议扩展或Swift语言功能可用于复制自动生成的结构初始值设定项,因为它基于元编程/代码生成,在这种情况下,由编译器生成

有一些工具,例如Sourcery(),允许您实现自己的元编程和代码生成。使用Sourcery,您可以在构建阶段运行脚本,自动为所需的
命令
枚举生成代码,并将其添加到符合
CommandHandler
的任何类型

这基本上是通过Swift编译器生成所需代码来模拟Codable的工作方式。但这两种情况都不是通过Swift语言功能(如协议扩展等)实现的。相反,它是由脚本编写的样板源代码,而不必手工编写


修订问题更新
如果只需确保有一种方法可以枚举CommandId枚举的所有情况,您就可以向
CommandId
协议添加协议要求,如下所示:

protocol CommandId {
    static var all: [Self] { get }
}
然后实现需要如下所示:

class HandlerA : CommandHandler {
    enum CommandIds : String, CommandId {
        case commandA1
        case commandA2

        static var all: [CommandIds] { return [.commandA1, .commandA2] }
    }
}
func processHandler<T:CommandHandler>(_ handler:T){
    T.CommandIds.all.forEach { // Do something with each command case }
}
您的流程功能可能如下所示:

class HandlerA : CommandHandler {
    enum CommandIds : String, CommandId {
        case commandA1
        case commandA2

        static var all: [CommandIds] { return [.commandA1, .commandA2] }
    }
}
func processHandler<T:CommandHandler>(_ handler:T){
    T.CommandIds.all.forEach { // Do something with each command case }
}
由于Swift主要是一种静态语言,运行时反射非常有限(目前),因此无法使用语言特性在运行时执行这些类型的任务

但没有什么能阻止您或任何开发人员像Swift编译器那样使用代码生成来实现类似的便利。事实上,当我向他介绍我在WWDC面临的一些挑战时,苹果公司Swift核心团队的一位知名成员甚至鼓励我这样做


还值得注意的是,现在是Swift编译器的一部分或具有要添加到Swift编译器的开放拉取请求的功能(如可编码、自动符合Equatable和Hashtable)在将它们添加到Swift本身之前,它们首先使用Sourcery在真实世界的Swift项目中创建和实施。

好的,我相信我已经准备好了所有的部分来展示如何在Swift中做到这一点。事实证明,我修改后的问题在如何做到这一点上几乎是正确的

下面是我用Swift 4编写的示例

首先,这里是如何定义实现此功能所需的协议。从设计角度来看,这些协议分别与CodableKey和Codable同义

protocol CommandId : EnumerableEnum, RawRepresentable {}

protocol CommandHandler{
    associatedtype CommandIds : CommandId
}
这里有一个协议及其相关扩展,用于使枚举的“case”值可枚举。您只需使枚举遵守
EnumerableEnum
协议,就可以得到一个“values”数组

由于上面的
CommandId
协议已经应用于相关的枚举,我们通过使其在自己的定义中也应用
EnumerableEnum
协议来简化事情。这样,我们只需要将
CommandId
应用于我们的枚举,就可以同时获得这两个协议

public protocol EnumerableEnum : Hashable {
    static var values: [Self] { get }
}

public extension EnumerableEnum {

    public static var values: [Self] {

        let valuesSequence = AnySequence { () -> AnyIterator<Self> in

            var caseIndex = 0

            return AnyIterator {
                let currentCase: Self = withUnsafePointer(to: &caseIndex){
                    $0.withMemoryRebound(to: self, capacity: 1){
                        $0.pointee
                    }
                }
                guard currentCase.hashValue == caseIndex else {
                    return nil
                }
                caseIndex += 1
                return currentCase
            }
        }

        return Array(valuesSequence)
    }
}
下面是一个接受
CommandHandler
类型的测试函数

func enumerateCommandIds<T:CommandHandler>(_ commandHandlerType:T.Type){

    for value in commandHandlerType.CommandIds.values{
        let caseName     = String(describing:value)
        let caseRawValue = value.rawValue

        print("\(caseName) = '\(caseRawValue)'")
    }
}

到达这里的路很长,风很大,但我们做到了!感谢大家的帮助!

您可以使用Swift的
CaseIterable
协议轻松做到这一点

protocolcommandid:caseitrable{
func句柄()
}
协议命令处理程序{
associatedtype CommandId:CommandId,可表示
}
类HandlerA:CommandHandler{
枚举CommandId:字符串,CommandId{
案例命令1
案例命令2
func句柄(){
打印(“\(原始值)已处理”)
}
}
}
类HandlerB:CommandHandler{
枚举CommandId:字符串,CommandId{
案例命令B1
案例命令B2
案例3
func句柄(){
打印(“\(原始值)已处理”)
}
}
}
func processHandler(handler:T){
//迭代CommandID的逻辑。但是您不必(实际上,不能!)将CodableKeys移到您定义为Codable的类型之外。我一直试图通过Swift源代码了解它们是如何做到的,但是
enumerateCommandIds(HandlerA.self)
// Outputs
//     commandA1 = '0'
//     commandA2 = '1'

enumerateCommandIds(HandlerB.self)
// Outputs
//     commandB1 = 'Command B1'
//     commandB2 = 'commandB2'
//     commandB3 = 'Yet another command'