使用协议中定义的默认参数实现函数

时间:2017-03-06 20:21:07

标签: swift protocols extension-methods swift-protocols swift-extensions

Swift协议可以通过向它们添加扩展来为函数和计算属性提供默认实现。我做了很多次。我的理解是默认实现仅用作“后备”:当类型符合协议但不提供自己的实现时执行。

至少这是我阅读The Swift Programming Language指南的方式:

  

如果符合类型提供了自己的必需方法或属性的实现,则将使用该实现而不是扩展提供的实现。

现在我遇到了一种情况,我实现特定协议的自定义类型为特定功能提供了一个实现,但它没有被执行 - 而是执行协议扩展中定义的实现。< / p>

作为示例,我定义了一个具有函数Movable的协议move(to:)和一个为此函数提供默认实现的扩展:

protocol Movable {

    func move(to point: CGPoint)

}

extension Movable {

    func move(to point: CGPoint = CGPoint(x: 0, y: 0)) {
        print("Moving to origin: \(point)")
    }

}

接下来,我定义一个符合Car的{​​{1}}类,但为Movable函数提供了自己的实现:

move(to:)

现在我创建一个新的class Car: Movable { func move(to point: CGPoint = CGPoint(x: 0, y: 0)) { print("Moving to point: \(point)") } } 并将其向下转换为Car

Movable

根据我是否为可选参数let castedCar = Car() as Movable 传递值,我观察到两种不同的行为:

  1. 为可选参数

    传递点

    point的实现称为

    Car

    输出:

      

    转到点:(20.0,10.0)

    1. 当我调用castedCar.move(to: CGPoint(x: 20, y: 10)) 函数而不提供可选参数的值时,move()的实现将被忽略

      调用Car协议的默认实现

      Movable

      输出:

        

      转移到原点:(0.0,0.0)

    2. 为什么?

1 个答案:

答案 0 :(得分:11)

这是因为呼叫

castedCar.move(to: CGPoint(x: 20, y: 10))

能够解析为协议要求func move(to point: CGPoint) - 因此调用将通过协议见证表(协议类型值实现多态的机制)动态调度,允许Car要调用的实现。

然而,电话

castedCar.move()

符合协议要求func move(to point: CGPoint)。因此,它不会通过协议见证表(仅包含协议要求的方法条目)发送给它。相反,当castedCar被输入为Movable时,编译器将不得不依赖静态分派。因此,将调用协议扩展中的实现。

默认参数值仅仅是函数的静态特性 - 编译器实际上只会发出一个函数的重载(一个 all 参数)。尝试通过排除其具有默认值的参数来应用函数将触发编译器插入对该默认参数值的评估(因为它可能不是常量),然后在调用站点插入该值。

因此,具有默认参数值的函数不能很好地与动态调度一起使用。使用默认参数值替换方法的类也可以获得意外结果 - 请参阅示例this bug report

获取默认参数值所需的动态分派的一种方法是在协议中定义static属性要求,以及协议扩展中的move()重载,它只是应用{ {1}}用它。

move(to:)

因为protocol Moveable { static var defaultMoveToPoint: CGPoint { get } func move(to point: CGPoint) } extension Moveable { static var defaultMoveToPoint: CGPoint { return .zero } // Apply move(to:) with our given defined default. Because defaultMoveToPoint is a // protocol requirement, it can be dynamically dispatched to. func move() { move(to: type(of: self).defaultMoveToPoint) } func move(to point: CGPoint) { print("Moving to origin: \(point)") } } class Car: Moveable { static let defaultMoveToPoint = CGPoint(x: 1, y: 2) func move(to point: CGPoint) { print("Moving to point: \(point)") } } let castedCar: Moveable = Car() castedCar.move(to: CGPoint(x: 20, y: 10)) // Moving to point: (20.0, 10.0) castedCar.move() // Moving to point: (1.0, 2.0) 现在是协议要求 - 可以动态调度它,从而为您提供所需的行为。

作为附录,请注意我们在defaultMoveToPoint而非defaultMoveToPoint上呼叫type(of: self)。这将为我们提供实例的动态元类型值,而不是调用该方法的静态元类型值,从而确保正确调度Self。但是,如果调用任何defaultMoveToPoint的静态类型(move()本身除外)就足够了,您可以使用Moveable

我更详细地讨论了协议扩展中可用的动态和静态元类型值之间的差异in this Q&A