Search code examples
iosswiftfunctiontypestypechecking

Allow Swift function parameter to be of multiple types


A function

func stepperValueChanged(_ myStepper: UIStepper) {
    // do stuff with myStepper
}

A second function

func switchValueChanged(_ mySwitch: UISwitch) {
    // do stuff with mySwitch
}

How do I create a third (alternative) function that can take either type?

func valueChanged(_ myComponent: /* ??? UIStepper or UISwitch, but nothing else ??? */) {
    // do stuff with myComponent
}

I've explored using enums, typealiases and protocols; which resulted in lots of interesting Stackoverflow reads but no solution.

Examples that don't work

// ** DON'T COPY AND PASTE, DONT WORK!! ** //
typealias validUIComponent = UIStepper, UISwitch
// or
typealias validUIComponent = UIStepper & UISwitch
// or
enum UIComponent { case stepper(UIStepper); case _switch(UISwitch) }
// or
protocol UIComponent { }
extension UIStepper: UIComponent { }
extension UISwitch: UIComponent { }
// ** DON'T COPY AND PASTE, DONT WORK!! ** //

Why would I want to do this? Type checking. I don't want any other UI element to be passed to the function.

I realise I could if let/guard let or some other form of checking once in the function body and bail as required, but this would only catch run-time not compile-time type errors.

Also I realise I could use Any? or (better) UIControl and downcast as needed.

func valueChanged(_ myComponent: UIControl) {
    // do stuff with
    myComponent as! UIStepper
    // do stuff with
    myComponent as! UISwitch
}

But is there a syntactic/more expressive solution?


Solution

  • You mentioned enums, which sound like an excellent fit for this use case. You can explicitly only require the types you expect, and nothing else. By adding a property to the enum you can then expose a UIControl property to interact as needed, without the need for down-casting (which is generally considered to be an anti-pattern).

    enum Component {
      case `switch`(UISwitch)
      case stepper(UIStepper)
      
      var control: UIControl {
        switch self {
          case .switch(let comp):
            return comp
          case .stepper(let comp):
            return comp
        }
      }
    }
    

    Then ask for a Component as a parameter to the function.

    func controlValueChanged(_ myComponent: Component) {
      // Now you can use them as a generic UIControl
      let control = myComponent.control
      
      // ...or different behaviours for each element
      switch myComponent {
        case .switch(let swit):
          // use the `swit`
        case .stepper(let step):
          // use the `step`
      }
    }
    

    Having said that, if the implementations for these types are totally different anyway, it may be more clear to define two separate functions.