kennyc
kennyc

Reputation: 5700

How to call an overloaded function when you only have a variable conforming to a lesser type?

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

Answers (2)

Rob Napier
Rob Napier

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

vacawama
vacawama

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

Related Questions