1

我有一个案例,我试图定义一个函数,该函数接收一组对象,并要求每个对象都必须定义一个名为“Commands”的基于字符串的枚举。

下面是一个示例,说明如果您使用关联类型,您将如何做到这一点:

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
    ]
}

问题是由于关联类型,您不能这样做:

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

注意:我试图阻止某人这样做,这应该会导致编译失败,因为 Raw 类型不是字符串。

class BadCommands : CommandSetProtocol {

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

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

如何实现这个(或类似的)?

4

2 回答 2

1

消除问题中的所有红鲱鱼,您实际上只是在指出众所周知的事实,即这种事情是合法的:

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

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

...但这种事情不是:

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

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

问题是你碰到了“只能用作通用约束”的墙。这堵墙将在未来的 Swift 版本中被拆除,但在那之前,制作该数组的方法是使用类型擦除

于 2018-02-06T20:10:00.543 回答
0

好的,我想出了如何得到我需要的东西。

如您所知,问题是我需要限制 RawRepresentable 的类型,这需要泛型/关联类型,但如果使用它,则无法将变量定义为该类型的数组。

但后来我问自己为什么需要那个。答案是因为我正在使用 RawRepresentable:String 来构建另一个 CommandDefinition 对象集合,这是我真正感兴趣的值。因此,解决方案是使用具有该第二级的第二级协议关联类型以满足不能拥有它们的第一级(基本)协议的要求。

这是上面的重写,添加了拼图的缺失部分。

首先,可以按原样添加到任何扩展项目的可重用框架。它由 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 }
    }
}

这是 CommandSet 的扩展,它使用 CommandSet 定义的“commands”关联类型来满足 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
            ]
        })
    }
}

这是命令集的特定于应用程序的实现以及使用它们的扩展。

首先,扩展本身......

class Extension : ExtensionBase {

    override var commandSets:[CommandSetBase.Type]{

        return [
            NavigationCommands.self,
            SelectionCommands.self
        ]
    }

    func extensionDidFinishLaunching() {

    }
}

现在选择命令:

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

最后,导航命令:

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

这是结果......

在此处输入图像描述

如果 Swift 允许您枚举枚举的情况,那么我可以消除上面 CommandSets 中看似多余的“静态 let 命令”。

于 2018-02-06T22:56:55.320 回答