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