【问题标题】:how to properly use a protocol that has an associated type to describe a property in another object如何正确使用具有关联类型的协议来描述另一个对象中的属性
【发布时间】:2020-01-20 19:06:27
【问题描述】:

我一直在创建一个 SwiftUI 组件,我希望这个组件有一个实现协议的属性。具体的用例是为图表绘制一个轴,它基于一个比例。

有几种具体的规模实现将数据从输入域转换为输出范围。我开始使用的两个是线性刻度,它从Double 输入域转换为用Double 表示的输出范围。另一种是基于日期/时间的比例,它从基于Date 的输入域转换为由Double 表示的输出范围。

规模是协议,我尝试将其大致定义为:

public protocol Scale {
    associatedtype InputDomain: Comparable // input domain

    var isClamped: Bool { get }

    // input values
    var domain: ClosedRange<InputDomain> { get }

    // output values
    var range: ClosedRange<Double> { get }

    /// converts a value between the input "domain" and output "range"
    ///
    /// - Parameter inputValue: a value within the bounds of the ClosedRange for domain
    /// - Returns: a value within the bounds of the ClosedRange for range, or NaN if it maps outside the bounds
    func scale(_ inputValue: InputDomain) -> Double

    /// converts back from the output "range" to a value within the input "domain". The inverse of scale()
    ///
    /// - Parameter outputValue: a value within the bounds of the ClosedRange for range
    /// - Returns: a value within the bounds of the ClosedRange for domain, or NaN if it maps outside the bounds
    func invert(_ outputValue: Double) -> InputDomain

    /// returns an array of the locations within the ClosedRange of range to locate ticks for the scale
    ///
    /// - Parameter count: a number of ticks to display, defaulting to 10
    /// - Returns: an Array of the values within the ClosedRange of the input range
    func ticks(count: Int) -> [InputDomain]
}

LinearScale 和 TimeScale 结构符合协议,分别定义了 typealias InputDomain = Doubletypealias InputDomain = Date

当我尝试使用此协议来描述与 SwiftUI 组件更通用的结构(比例)时,问题就出现了:

public struct AxisView: View {    
    let scale:  Scale

    public var body: some View { ... }
}

编译器提供错误: Protocol 'Scale' can only be used as a generic constraint because it has Self or associated type requirements

我不确定解决此问题的最佳方法,即解决编译器错误/约束。我应该做些什么来使 SwiftUI 组件成为泛型,还是不应该使用协议的关联类型?

或者是否有其他方法可以考虑使用协议和结构来构建此代码以支持各种比例类型?

更新:我得到了原始问题的答案,但它并不完全适合我。我将泛型定义添加到封闭类型(我的Scale 实现)。

我不清楚为什么需要这样做?在封闭结构上添加通用标记后,编译器错误消失了。我假设这是一个 swift 编译器可以采取多种选择的地方,并告诉它“是的,我希望这是一个通用的”是一条路径 - 其他可能的路径是什么?

我还注意到,即使它被定义为泛型类,我使用的特定类也经常被 swift 编译器推断出来。所以我不需要用通用语法完全指定类型。例如,我可以使用

LinearScale() 而不是LinearScale&lt;Double&gt;(),它会推断出正确的泛型。这是预期的吗?

【问题讨论】:

  • 这是一种糟糕的风格,但您可以将 scale 声明为 Any 并在必要时打开它。

标签: swift generics swift-protocols


【解决方案1】:

你也应该让你的视图通用:

public struct AxisView<ScaleType: Scale>: View {    
    let scale:  ScaleType    
    public var body: some View { ... }
}

【讨论】:

  • 感谢您提供正确方向的指针 - 我已经稍微更新了问题,因为我不完全确定当我将视图设为 Generic 时会发生什么。编译器似乎对此进行了很多推断,那么为什么它不能推断出我也需要一个通用结构呢?这里有没有我遗漏的其他选择或模式?
  • @heckj swift 编译器可以设计为建议这样的更改,但设计者没有这样做,可能是因为它非常辛苦并且没有带来太多好处。另一方面,如果你的意思是 swift 应该不理会这段代码,只是“假设”你实际上是想让 AxisView 通用,那么我认为这太过分了。你不需要&lt;Double&gt;,因为你已经在LinearScale的声明中说过InputDomain是什么。
猜你喜欢
  • 2020-11-14
  • 1970-01-01
  • 2022-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多