Без использования связанного типа, вы можете ограничить переменную в протоколе только RawRepresentables, где необработанным типом является String?

У меня есть случай, когда я пытаюсь определить функцию, которая принимает массив объектов с требованием, чтобы каждый объект определял перечисление на основе строки под названием "Команды".

Вот пример того, как бы вы это сделали, если бы использовали связанный тип:

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

Как этого (или аналогичного) достичь?

2 ответа

Устраняя все красные селедки в вопросе, вы на самом деле просто указываете на общеизвестный факт, что подобные вещи законны:

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, но до тех пор способ создать этот массив - использовать стирание типов.

Хорошо, я понял, как получить то, что мне нужно.

Как вы знаете, проблема в том, что мне нужно было ограничить тип 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 связанный с типом 'command' тип для удовлетворения требования CommandSetBase для commandDefinitions (это был отсутствующий фрагмент):

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 когда-либо позволит вам перечислить случаи перечисления, то я мог бы исключить кажущиеся избыточными "статические команды let" в CommandSets выше.

Другие вопросы по тегам