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