您如何能够镜像代码/代码可靠的协议的设计



我正在尝试实现类似于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)

我在这里为processHandler内的代码苦苦挣扎,因为我不确定如何从处理程序实例达到枚举值。

那我想念什么?获得相关枚举的值的代码是什么?

好吧,我相信我有所有的作品可以展示如何显示您如何在迅速做到这一点。事实证明,我修订的问题正是正确地做正确的边缘。

这是我用 swift 4 写的示例...

首先,这是您定义完成此工作所需的协议的方法。从设计的角度来看,这些分别与CodableKeys和Codable的代名词。

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

这是一个协议及其关联的扩展,以使枚举的"情况"值。您只需使枚举遵守EnumerableEnum协议,然后获得"值"数组。

由于上面的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/CommandId协议

的两个类
class HandlerA : CommandHandler{
    enum CommandIds : Int, CommandId{
        case commandA1
        case commandA2
    }
}
class HandlerB : CommandHandler{
    enum CommandIds : String, CommandId{
        case commandB1 = "Command B1"
        case commandB2
        case commandB3 = "Yet another command"
    }
}

这是一个接受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)'")
    }
}

最后,这是运行该测试的结果

enumerateCommandIds(HandlerA.self)
// Outputs
//     commandA1 = '0'
//     commandA2 = '1'
enumerateCommandIds(HandlerB.self)
// Outputs
//     commandB1 = 'Command B1'
//     commandB2 = 'commandB2'
//     commandB3 = 'Yet another command'

到这里是一条漫长的大风道路,但我们做到了!感谢所有人的帮助!

您可以使用Swift的CaseIterable协议轻松地执行此操作。

protocol CommandId: CaseIterable {
    func handle()
}
protocol CommandHandler {
    associatedtype CommandIds: CommandId, RawRepresentable
}
class HandlerA: CommandHandler {
    enum CommandIds: String, CommandId {
        case commandA1
        case commandA2
        func handle() {
            print("(rawValue) is handled")
        }
    }
}
class HandlerB: CommandHandler {
    enum CommandIds: String, CommandId {
        case commandB1
        case commandB2
        case commandB3
        func handle() {
            print("(rawValue) is handled")
        }
    }
}
func processHandler<T: CommandHandler>(_ handler: T) {
    // Logic to iterate over CommandIds. <-- This is where I get stumped
    T.CommandIds.allCases.forEach({ $0.handle() })
}
let handlerA = HandlerA()
processHandler(handlerA)

在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)

我想到的唯一解决方案是使用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语言本身可以模仿编码,因为Codable的实现依赖于编译器生成特殊案例代码。具体而言,没有协议扩展名创建默认编码键,编译器会在一种类型内部枚举的类型中枚举,除非您自己指定。

这类似于Swift编译器将如何自动为structs创建初始化器("成员Wision Winializer"),除非您指定自己的初始化程序。在这种情况下,也没有协议扩展或SWIFT语言功能您可以用于复制自动生成的struct Initializer,因为它基于编译器的元编程/代码生成。

有工具,例如源(https://github.com/krzysztofzablocki/sourcery),可以使您可以实现自己的元编程和代码生成。使用Sourcery,您可以在构建阶段运行一个脚本,该脚本将自动生成所需的Command枚举的代码,并将其添加到符合CommandHandler的任何类型中。

这实质上将模仿Codable如何通过Swift编译器生成所需的代码工作。但是在两种情况下,它都是通过swift语言功能(例如协议扩展等)完成的。相反,它是由脚本编写而不是必须手工编写的样板源代码。


修订问题的更新


如果简单地确保有办法列举所有需要枚举的所有情况,那么您始终可以将协议要求添加到类似的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 }
}

不过,值得继续注意到,对于代码,Swift没有或使用任何语言功能来列举所有情况。取而代之的是,编译器使用对代码符合形式类型的所有属性的知识来生成该类型的init(from decoder: Decoder)的特定实现,包括每种情况的行,基于已知的属性名称和类型,例如

// This is all the code a developer had to write
struct Example: Codable {
  let name: String
  let number: Int
}
// This is all source code generated by the compiler using compiler reflection into the type's properties, including their names and types
extension Example {
  enum CodingKeys: String, CodingKey {
    case name, number
  }
  init(from decoder: Decoder) throws {
    let values = try decoder.container(keyedBy: CodingKeys.self)
    name = try values.decode(String.self, forKey: .name)
    number = try values.decode(Int.self, forKey: .number)
  }
}

由于Swift是一种静态语言,其运行时反射极有限(目前),因此使用语言功能在运行时无法执行这些类型的任务。

但是,没有什么可以阻止您或任何开发人员使用代码生成,就像Swift编译器完成类似便利的方式一样。实际上,当我向WWDC面对的一些挑战时,苹果的一位著名成员甚至鼓励我这样做。

还值得注意的是,现在首先在现实世界中创建和实现了现在已添加到Swift编译器(例如Codable和Automation conformance and equatable and Asshable)中的Swift Replect的功能或已添加到Swift Compiler的开放请求(例如Codable和自动符合)在源中,在将其添加到Swift之前,Swift项目。

最新更新