Reputation: 5700
Is there a way for Swift to correctly infer which overloaded function to call given a variable that conforms to a common protocol of each of the overloaded parameters?
In the code example below, I want to be able to pass a generic object of type Command
to a number of other objects that might be able to process it.
However, the type information is "lost" when an object is downcast to the base protocol. This prevents me from being able to use function overloading based on the type of the command.
Is there a way to get that type information "back" so that Swift has enough information to correctly call the appropriate function?
protocol Command {}
protocol ToDoCommand : Command {}
protocol UserCommand : Command {}
struct AddToDoCommand : ToDoCommand {}
struct EditToDoCommand : ToDoCommand {}
struct AddUserCommand : UserCommand {}
struct EditUserCommand : UserCommand {}
class ToDoManager {
func performCommand(_ command:Command) {
guard let todoCommand = command as? ToDoCommand else {
return
}
// Perform some tasks that are common to all ToDoCommands...
// This produces a compiler error because 'todoCommand' is of
// type ToDoCommand, which is not specific enough for Swift
// to deduce which overloaded function to call. Can this
// be overcome?
performToDoCommand(todoCommand)
}
func performToDoCommand(_ command:AddToDoCommand) {
print("Add ToDo")
}
func performToDoCommand(_ command:EditToDoCommand) {
print("Edit ToDo")
}
}
class UserManager {
func performCommand(_ command:Command) {
guard let userCommand = command as? UserCommand else {
return
}
// Perform some tasks that are common to all UserCommands...
// See note above...
performUserCommand(userCommand)
}
func performUserCommand(_ command:AddUserCommand) {
print("Add User")
}
func performUserCommand(_ command:EditUserCommand) {
print("Edit User")
}
}
let todoManager = ToDoManager()
let userManager = UserManager()
let command = AddUserCommand()
todoManager.performCommand(command)
userManager.performCommand(command)
Upvotes: 4
Views: 117
Reputation: 299345
I think you're trying to recreate class inheritance. Instead, you want composition. You've created a bunch of empty protocols that are acting like abstract classes. That's not the right way to think about protocols. I think what you want are just structs.
// A command is just something that can be performed
struct Command {
let perform: () -> Void
}
// And there are lots of ways to make them
extension Command {
// We can make command types that wrap other command types
static func makeToDo(additional: @escaping () -> Void) -> Command {
return Command {
// common todo behavior
additional()
}
}
}
// And we can just make regular commands
extension Command {
// Things that include ToDo
static func makeAddToDo() -> Command { makeToDo { print("Add ToDo") } }
static func makeEditToDo() -> Command { makeToDo { print("Edit ToDo") }}
// Things that don't
static func makeAddUser() -> Command { Command{print("Add User")}}
static func makeEditUser() -> Command { Command{print("Edit User")}}
}
There is now no reason for a UserManager that ignores things sent to it or a ToDoManager that ignores things sent to it. Those are really confusing ways to do this. "Here's a command. Please perform it if you know how, but ignore it if you don't." What are you supposed to do if none of the managers know how to perform the command? Or multiple managers?
Instead, you just create a bunch of commands and then call .perform()
.
Upvotes: 2
Reputation: 154583
There are a couple of ways to go about this...
Use a switch
to reestablish the type
Swift needs to know at compile time which overloaded function it is calling. This can't happen if Swift doesn't know at compile time which type of the variable it has.
To get the type information back, you can use a switch
to reestablish the type:
func performCommand(_ command:Command) {
guard let todoCommand = command as? ToDoCommand else {
return
}
// Perform some tasks that are common to all ToDoCommands...
switch todoCommand {
case let command as AddToDoCommand:
performCommand(command)
case let command as EditToDoCommand:
performCommand(command)
default: break
}
}
Use polymorphism
A way to let Swift decide which performToDoCommand()
command to run at runtime is to use polymorphism.
Add the requirement to implement func performToDoCommand()
to the ToDoCommand
protocol, and then implement that for each struct
that conforms to ToDoCommand
. Calling the right one is then simple...
protocol Command {}
protocol ToDoCommand : Command {
func performToDoCommand()
}
protocol UserCommand : Command {}
struct AddToDoCommand : ToDoCommand {
func performToDoCommand() {
print("Add ToDo")
}
}
struct EditToDoCommand : ToDoCommand {
func performToDoCommand() {
print("Edit ToDo")
}
}
struct AddUserCommand : UserCommand {}
struct EditUserCommand : UserCommand {}
class ToDoManager {
func performCommand(_ command:Command) {
guard let todoCommand = command as? ToDoCommand else {
return
}
todoCommand.performToDoCommand()
}
}
Upvotes: 2