简体   繁体   English

在不使用关联类型的情况下,您能否将协议中的变量限制为原始类型为 String 的 RawRepresentables?

[英]Without using an associated type, can you restrict a variable in a protocol to only RawRepresentables where the raw type is String?

I have a case where I am trying to define a function that takes in an array of objects with the requirement that each object must define a string-based enum called 'Commands'.我有一个案例,我试图定义一个函数,该函数接受一个对象数组,要求每个对象必须定义一个名为“命令”的基于字符串的枚举。

Here's an example of how you would do it if you were using an associated type:这是一个示例,说明如果您使用关联类型,您将如何执行此操作:

protocol CommandSetProtocol {

    associatedtype Command : RawRepresentable where Command.RawValue == String

    var commands:[Command] { get }
}

class FooCommands : CommandSetProtocol {

    enum Command : String {
        case commandA = "Command A"
        case commandB = "Command B"
    }

    let commands = [
        Command.commandA,
        Command.commandB
    ]
}

class LaaCommands : CommandSetProtocol {

    enum Command : String {
        case commandC = "Command C"
        case commandD = "Command D"
    }

    let commands = [
        Command.commandC,
        Command.commandD
    ]
}

The problem is you can't do this because of that associated type:问题是你不能这样做,因为关联类型:

var commandSets:[CommandSetProtocol.Type] = [
    FooCommands.self,
    LaaCommands.self
]

Of note: I'm trying to stop someone form doing this, which should fail compilation because the Raw type is not a string.注意:我试图阻止某人这样做,这应该会导致编译失败,因为 Raw 类型不是字符串。

class BadCommands : CommandSetProtocol {

    enum Command : Int {
        case commandE = 1
        case commandF = 2
    }

    let commands = [
        Command.commandE,
        Command.commandF
    ]
}

How can this (or similar) be achieved?如何实现这个(或类似的)?

Eliminating all the red herrings in the question, you are really just pointing out the well-known fact that this sort of thing is legal:消除问题中的所有红鲱鱼,您实际上只是指出了众所周知的事实,即此类事情是合法的:

protocol P {}
class A:P {}
class B:P {}

let arr : [P] = [A(), B()]

... but this sort of thing is not: ......但这种事情不是:

protocol P {associatedtype Assoc}
class A:P {typealias Assoc=String}
class B:P {typealias Assoc=String}

let arr : [P] = [A(), B()]

The problem is that you hit the "can only be used as a generic constraint" wall.问题是你碰到了“只能用作通用约束”的墙。 That wall is due to be torn down in a future version of Swift, but until then, the way to make that array is to use type erasure .这堵墙将在 Swift 的未来版本中拆除,但在那之前,制作该数组的方法是使用类型擦除

Ok, I figured out how to get what I needed.好的,我想出了如何获得我需要的东西。

As you know, the issue is I needed to restrict the type of the RawRepresentable, which requires a generic/associated type, but if you use that, then you can't define a variable as an array of that type.如您所知,问题是我需要限制 RawRepresentable 的类型,这需要泛型/关联类型,但是如果使用它,则无法将变量定义为该类型的数组。

But then I asked myself why did I need that.但后来我问自己为什么需要那个。 The answer is because I'm using that RawRepresentable:String to build up another collection of CommandDefinition objects, and it's that value that I'm really interested in. So, the solution was to use a second level of protocols with that second level having the associated type to satisfy the requirements of the first level (base) protocol which can't have them.答案是因为我正在使用 RawRepresentable:String 来构建另一个 CommandDefinition 对象集合,而我真正感兴趣的是这个值。因此,解决方案是使用第二级协议,第二级具有关联类型以满足不能拥有它们的第一级(基础)协议的要求。

Here's the above re-written, with the missing pieces of the puzzle added.这是上面重写的内容,添加了拼图的缺失部分。

First, the reusable framework that can be added to any extension project as-is.首先,可以按原样添加到任何扩展项目的可重用框架。 Its comprised of the CommandSetBase, CommandSet, ExtensionBase and an extension on CommandSet:它由 CommandSetBase、CommandSet、ExtensionBase 和 CommandSet 上的扩展组成:

typealias CommandDefinition = [XCSourceEditorCommandDefinitionKey: Any]

protocol CommandSetBase : XCSourceEditorCommand {

    static var commandDefinitions : [CommandDefinition] { get }
}

protocol CommandSet : CommandSetBase {

    associatedtype Command : RawRepresentable where Command.RawValue == String

    static var commands:[Command] { get }
}

class ExtensionBase : NSObject, XCSourceEditorExtension {

    var commandSets:[CommandSetBase.Type]{
        return []
    }

    final var commandDefinitions: [CommandDefinition] {
        return commandSets.flatMap{ commandSet in commandSet.commandDefinitions }
    }
}

Here's the extension for CommandSet that uses the CommandSet-defined 'commands' associated type to satisfy the CommandSetBase requirement of the commandDefinitions (this was the missing piece):这是 CommandSet 的扩展,它使用 CommandSet 定义的“命令”关联类型来满足 commandDefinitions 的 CommandSetBase 要求(这是缺失的部分):

extension CommandSet {

    static var commandDefinitions:[CommandDefinition] {

        return commands.map({

            command in

            return [
                XCSourceEditorCommandDefinitionKey.classNameKey  : String(reflecting:self),
                XCSourceEditorCommandDefinitionKey.identifierKey : String(describing:command),
                XCSourceEditorCommandDefinitionKey.nameKey       : command.rawValue
            ]
        })
    }
}

And here's the app-specific implementation of the command sets and the extension that uses them.这是命令集的特定于应用程序的实现以及使用它们的扩展。

First, the extension itself...首先,扩展本身...

class Extension : ExtensionBase {

    override var commandSets:[CommandSetBase.Type]{

        return [
            NavigationCommands.self,
            SelectionCommands.self
        ]
    }

    func extensionDidFinishLaunching() {

    }
}

Now the Selection commands:现在选择命令:

class SelectionCommands: NSObject, CommandSet {

    enum Command : String {
        case align            = "Align"
        case alignWithOptions = "Align with options..."
    }

    static let commands = [
        Command.align,
        Command.alignWithOptions
    ]

    func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {

        print("You executed the Selection command \(invocation.commandIdentifier)")

        completionHandler(nil)
    }
}

And lastly, the Navigation commands:最后,导航命令:

class NavigationCommands : NSObject, CommandSet {

    enum Command : String {
        case jumpTo = "Jump to..."
        case goBack = "Go back"
    }

    static let commands = [
        Command.jumpTo,
        Command.goBack
    ]

    func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {

        print("You executed the Navigation command \(invocation.commandIdentifier)")

        completionHandler(nil)
    }
}

And here's the result...这是结果......

在此处输入图片说明

If Swift ever allows you to enumerate the cases of an enum, then I could eliminate the seemingly-redundant 'static let commands' in the CommandSets above.如果 Swift 允许您枚举枚举的情况,那么我可以消除上面 CommandSets 中看似冗余的“静态 let 命令”。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM