【问题标题】:Allow Swift function parameter to be of multiple types允许 Swift 函数参数为多种类型
【发布时间】:2021-10-04 23:32:39
【问题描述】:

一个函数

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

第二个功能

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

如何创建可以采用任一类型的第三个(替代)函数?

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

我探索过使用枚举、类型别名和协议;这导致了很多有趣的 Stackoverflow 读取,但没有解决方案。

无效的示例

// ** 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!! ** //

我为什么要这样做?类型检查。我不希望将任何其他 UI 元素传递给该函数。

我意识到如果 let/guard let 或某种其他形式的检查在函数体中一次并根据需要保释,我可以,但这只会捕获运行时而不是编译时类型错误。

我也意识到我可以使用 Any?或(更好的)UIControl 并根据需要向下转换。

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

但是有句法/更具表现力的解决方案吗?

【问题讨论】:

标签: ios swift function types typechecking


【解决方案1】:

您提到了枚举,这听起来非常适合这个用例。 您可以明确地只需要您期望的类型,而不是其他类型。 通过向枚举添加属性,您可以公开 UIControl 属性以根据需要进行交互,而无需向下转换(这通常被认为是一种反模式)。

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
    }
  }
}

然后请求Component 作为函数的参数。

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`
  }
}

话虽如此,如果这些类型的实现无论如何都完全不同,那么定义两个单独的函数可能会更清楚。

【讨论】:

  • (删除之前的评论,如果被视为意外发布)太好了,谢谢,我了解您的所作所为/您的方法。 Swift 新手(但不是编程)。迷人的语言。提出/询问几点: 1. 不知道向下转换被认为是一种反模式。那很有意思。 2. 你在switch 上使用了反引号。没见过。有这个名字吗?所以我可以研究 3. 在“case .stepper(let step):”之前看到过这个模式。不太明白它为什么起作用(步骤来自哪里)。它也有名字,所以我可以研究一下?谢谢。
  • @Jim 1. 向下转换通常表明类型信息在某处“丢失”了,所以如果可能的话最好避免这种情况(泛型也有帮助) 2. switch 是保留字,所以backticks allow us to use it as a variable name 3. 请参阅Enumarations - associated values 的 Swift 语言指南。
  • 标记为已接受的答案,因为它最直接地回答了我的问题,不仅提供了解决方案,而且还以我认为有效的方式提供了一个解决方案。
【解决方案2】:

我认为你在这里把事情复杂化了,没有必要通过引入协议或新类型(枚举)来产生任何开销。

相反,我会通过使用多态性并声明具有相同签名的多个函数来处理这个问题。

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

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

这将使调用代码保持整洁,每个函数的职责清晰。

如果两个函数之间有一些公共代码,则将其提取到第三个公共函数中

【讨论】:

  • 是的,这是一个有效的论点,我在询问时考虑了它。也许问题是(或在询问中)更多的是兴趣点,而不是实际的生产解决方案。因此,以“但是有句法/更具表现力的解决方案吗?”来结束这个问题。而不是 htf 我这样做我有代码可以在 5 分钟内交付?! :) 也就是说,在我的实际用例中,你从我的问题中不会知道,valueChanged 是从一个地方调用的回调,它不想在响应 UIControl 的类型之间做出决定。一个特定的用例,但仍然是一个。
  • 啊,现在我重新阅读了您的答案。我的要求“不想在响应 UIControl 的类型之间做出决定。”无论如何仍然满足不是吗?调用者只会调用符合签名的函数?哇! Swift 真的很强大。
  • 是的,只要您对每种类型的控件都有一个功能,就可以通过这种方式实现,而且,IMO,这是一个更清洁的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-05
  • 1970-01-01
  • 2020-03-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多