了解 swift 中的协议扩展

understanding protocol extensions in swift

我正在尝试像这样实现一个基本的协议扩展:

protocol Value {
    func get() -> Float
    mutating func set(to:Float)
}
extension Value {
    static func min(of a:Value, and b:Value) -> Float {
        if a < b { //Expression type 'Bool' is ambiguous without more context
            return a.get()
        }else{
            return b.get()
        }
    }
    static func < (a:Value, b:Value) -> Bool {
        return a.get() < b.get()
    }
}

if 子句中,编译器说:Expression type 'Bool' is ambiguous without more context。为什么这不起作用?

你不会写

if a < b {

因为 ab 的类型 Value 不是 Comparable.

但是您可以比较与 ab

关联的 float
if a.get() < b.get() {

如果您希望能够创建可以使用 ><== 等运算符的类型,它们必须符合 Comparable协议:

 protocol Value: Comparable {
    func get() -> Float
    mutating func set(to: Float)
}

虽然这有更多的限制。您必须将协议扩展中的所有 Value 类型更改为 Self:

extension Value {
    static func min(of a: Self, and b: Self) -> Float {
        if a < b { //Expression type 'Bool' is ambiguous without more context
            return a.get()
        }else{
            return b.get()
        }
    }

    static func < (a: Self, b: Self) -> Bool {
        return a.get() < b.get()
    }
}

Self 类型被替换为实现协议的类型。因此,如果我在类型 Container 上实现 Value,方法签名将如下所示:

class Container: Value {
    static func min(of a: Container, and b: Container) -> Float

    static func < (a: Container, b: Container) -> Bool
}

附带说明一下,如果您希望 Value 符合 Comparable,您可能还想将 == 运算符添加到 Value 扩展中:

static func <(lhs: Self, rhs: Self) -> Bool {
    return lhs.get() < rhs.get()
}

所述,作为 static 成员实现的运算符重载与作为顶级函数实现的运算符重载之间存在差异。 static 成员采用额外的(隐式)self 参数,编译器需要能够推断出该参数。

那么self的值是如何推断出来的呢?好吧,它必须从操作数或 return 类型的重载中完成。对于协议扩展,这意味着其中一种类型需要 Self。请记住,您不能直接调用类型上的运算符(即您不能说 (Self.<)(a, b))。

考虑以下示例:

protocol Value {
  func get() -> Float
}

extension Value {
  static func < (a: Value, b: Value) -> Bool {
    print("Being called on conforming type: \(self)")
    return a.get() < b.get()
  }
}

struct S : Value {
  func get() -> Float { return 0 }
}

let value: Value = S()
print(value < value) // Ambiguous reference to member '<'

调用 <self 的值是多少?编译器无法推断它(我真的认为它应该直接在重载上出错,因为它是不可调用的)。请记住,协议扩展中静态范围内的 self 必须是符合 concrete 的类型;它不能只是 Value.self(因为协议扩展中的静态方法仅可用于调用具体的符合类型,而不是协议类型本身)。

我们可以通过将重载定义为顶级函数来修复上面的示例和您的示例:

protocol Value {
  func get() -> Float
}

func < (a: Value, b: Value) -> Bool {
  return a.get() < b.get()
}

struct S : Value {
  func get() -> Float { return 0 }
}

let value: Value = S()
print(value < value) // false

这是有效的,因为现在我们不需要推断 self 的值。

我们还可以为编译器提供一种方法来推断 self 的值,方法是使一个或两个参数采用 Self:

protocol Value {
  func get() -> Float
}

extension Value {
  static func < (a: Self, b: Self) -> Bool {
    print("Being called on conforming type: \(self)")
    return a.get() < b.get()
  }
}

struct S : Value {
  func get() -> Float { return 0 }
}

let s = S()
print(s < s)

//  Being called on conforming type: S
//  false

编译器现在可以从操作数的静态类型推断出self。然而,如上所述,这需要是一个具体类型,所以你不能处理 异构 Value 操作数(你可以使用一个操作数 Value; 但不能两者兼而有之,因为那样就无法推断 self).


尽管请注意,如果您提供 < 的默认实现,您可能还应该提供 == 的默认实现。除非你有充分的理由不这样做,否则我还建议你让这些重载采用同质的具体操作数(即 Self 类型的参数),这样它们就可以为 Comparable.[= 提供默认实现45=]

此外,我建议不要有 get()set(to:) 要求,而是建议使用可设置的 属性 要求:

// Not deriving from Comparable could be useful if you need to use the protocol as
// an actual type; however note that you won't be able to access Comparable stuff,
// such as the auto >, <=, >= overloads from a protocol extension.
protocol Value {
  var floatValue: Double { get set }
}

extension Value {

  static func == (lhs: Self, rhs: Self) -> Bool {
    return lhs.floatValue == rhs.floatValue
  }

  static func < (lhs: Self, rhs: Self) -> Bool {
    return lhs.floatValue < rhs.floatValue
  }
}

最后,如果 Comparable 一致性对于 Value 的一致性是必不可少的,你应该让它派生自 Comparable:

protocol Value : Comparable {
  var floatValue: Double { get set }
}

在任何一种情况下都不需要 min(of:and:) 函数,因为当符合类型符合 Comparable 时,它可以使用顶级 min(_:_:) 函数。