2

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?

Jim
  • 179
  • 3
  • 12

2 Answers2

3

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.

Bradley Mackey
  • 6,777
  • 5
  • 31
  • 45
  • 1
    (deleted previous comment if seen as posted by accident) That's great, thanks, and I understand what you've done/your approach. New to Swift (but not programming). Fascinating language. A few points to raise/ask: 1. Didn't know down casting considered an anti-pattern. That's interesting. 2. Your use of backticks on `switch`. Not seen that. Has that a name? So I can research 3. Seen this pattern before "case .stepper(let step):". Don't really understand why it works (where step comes from). Has it a name too, so I can research? Thanks. – Jim Jul 29 '21 at 10:09
  • 1
    @Jim 1. Downcasting generally indicates that type information was "lost" somewhere, so it's best to avoid this if possible (generics help with this too) 2. `switch` is a reserved word, so [backticks allow us to use it as a variable name](https://stackoverflow.com/questions/41503740/swift-variable-name-with-backtick) 3. See the Swift Language Guide for [Enumarations - associated values](https://docs.swift.org/swift-book/LanguageGuide/Enumerations.html). – Bradley Mackey Jul 29 '21 at 10:13
  • Marked as accepted answer as it most directly answers my question not only in providing a solution but also providing one in the way that I imagined was a valid approach. – Jim Jul 31 '21 at 07:30
1

I think you are over complicating things here and that there is no need to create any overhead by introducing a protocol or a new type (enum).

Instead I would handle this by using polymorphism and declaring multiple functions with the same signature.

func controlValueChanged(_ switch: UISwitch) {
    // code…
}

func controlValueChanged(_ stepper: UIStepper) {
    // code…
}

This will keep the calling code nice and tidy and the responsibility of each function clear.

If there are some common code between the two functions then extract that into a third common function

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • Yes that's a valid argument and I considered it when asking. Perhaps the question is (or became in the asking) more a point of interest rather than a practical production solution. Hence concluding the question with "But is there a syntactic/more expressive solution?" rather than htf do I do this I've got code to deliver in 5mins?! :) That said, in my actual use case, which you obvs wouldn't know from my question, valueChanged is a callback invoked from a single place that doesn't want to have to decide between the type of responding UIControl. A specific use case sure, but one nonetheless. – Jim Jul 29 '21 at 11:30
  • Ah, now that I've re-read your answer. My requirement "doesn't want to have to decide between the type of responding UIControl." is still fulfilled anyway isn't it? The caller will just call the function that fits the signature? WOW! Swift's really powerful. – Jim Jul 29 '21 at 11:34
  • 1
    Yes it is fulfilled this way as long as you have a function for each type of control and, IMO, this is a cleaner solution. – Joakim Danielson Jul 29 '21 at 11:37