一尘不染

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

swift

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

至少这就是我阅读
Swift编程语言》
指南的方式:

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

现在,我遇到了这样的情况:我的实现某种协议的自定义类型 确实 为特定功能提供了一种实现,但是并未执行-而是执行协议扩展中定义的实现。


例如 ,我定义了一个协议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))

输出:

移至点:(20.0,10.0)


  1. 当我调用move()功能, 而无需为可选参数提供一个值Car的执行将被忽略,

Movable协议的默认实现是不是

    castedCar.move()

输出:

移至原点:(0.0,0.0)


为什么?


阅读 211

收藏
2020-07-07

共1个答案

一尘不染

这是由于该呼叫

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

能够满足协议要求func move(to point: CGPoint)-因此,该调用将通过协议见证表(协议类型的值实现多态性的机制)动态分派到,从而允许Car调用的实现。

但是,电话

castedCar.move()

不会 匹配协议的要求func move(to point: CGPoint)。因此,它不会通过协议见证表(仅包含协议 要求的
方法条目)进行调度。相反,如castedCar键入为Movable,编译器将不得不依赖静态调度。因此,将调用协议扩展中的实现。

默认参数值只是函数的静态功能-编译器实际上只会发出函数的单个重载(一个带有 所有
参数的重载)。尝试通过排除具有默认值的参数之一来应用函数,将触发编译器插入该默认参数值的评估值(因为它可能不是恒定的),然后在调用位置插入该值。

因此,具有默认参数值的函数不能很好地与动态分配配合使用。您还可以使用具有默认参数值的类覆盖方法获得意想不到的结果-
例如,参见此错误报告


获得默认参数值所需的动态调度的一种方法是static在协议中定义属性要求,以及move()协议扩展中的过载(仅适用于此)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现在是协议要求–可以将其动态分配给您,从而为您提供所需的行为。

作为附录,请注意,我们调用defaultMoveToPointtype(of: self),而不是Self。这将为我们提供实例的 动态元
类型值,而不是方法调用时的静态元类型值,从而确保defaultMoveToPoint正确分派。但是,如果move()调用的任何对象的静态类型(Moveable本身除外)就足够了,则可以使用Self

2020-07-07