swift - 使用协议(protocol)中定义的默认参数实现函数

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

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

至少我是这样读的The Swift Programming Language指南:

If a conforming type provides its own implementation of a required method or property, that implementation will be used instead of the one provided by the extension.

现在我遇到了这样一种情况,我实现某个协议(protocol)的自定义类型确实为特定功能提供了一个实现,但它没有被执行——而是执行了协议(protocol)扩展中定义的实现。


举个例子,我定义了一个协议(protocol)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 类,它符合 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

根据我是否为可选参数 point 传递值,我观察到两种不同的行为:


  1. 为可选参数传递一个点

    调用Car的实现:

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

    输出:

    Moving to point: (20.0, 10.0)


  1. 当我调用 move() 函数时没有为可选参数提供值 Car 的实现被忽略和

    Movable 协议(protocol)的默认实现被调用:

    castedCar.move()
    

    输出:

    Moving to origin: (0.0, 0.0)


为什么?

最佳答案

这是因为调用

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

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

但是,调用

castedCar.move()

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

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

出于这个原因,具有默认参数值的函数根本无法很好地与动态调度配合使用。您还可以通过使用默认参数值覆盖方法的类获得意想不到的结果——参见示例 this bug report .


为默认参数值获取所需的动态调度的一种方法是在协议(protocol)中定义 static 属性要求,以及 move() 重载在一个简单地应用 move(to:) 的协议(protocol)扩展中。

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 现在是一项协议(protocol)要求 - 它可以被动态调度到,从而为您提供所需的行为。

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

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

关于swift - 使用协议(protocol)中定义的默认参数实现函数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42634816/

相关文章:

swift - 为什么我的 Facebook 插页式广告没有显示在我的应用程序中?

ios - 弱委托(delegate)和类协议(protocol)

swift - 如何将关联类型约束为协议(protocol)

c#-4.0 - 扩展方法可以修改扩展类值吗?

ios - 使用 Swift 3 拉动刷新和 Alamofire

swift - iOS 11.4.1 中应用程序处于后台时静默通知不起作用

swift - 为什么不应该直接扩展 UIView 或 UIViewController?

c# - 通用扩展方法解析失败

Angular 7 向原语添加扩展方法

swift - 默认参数swift