至少那是我读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.
现在我遇到了这样一种情况:我实现特定协议的自定义类型确实为特定函数提供了一个实现,但它没有被执行 – 而是执行协议扩展中定义的实现.
作为一个例子,我定义了一个具有函数move(to :)的协议Movable和一个为这个函数提供默认实现的扩展:
protocol Movable { func move(to point: CGPoint) } extension Movable { func move(to point: CGPoint = CGPoint(x: 0,y: 0)) { print("Moving to origin: \(point)") } }
接下来,我定义一个符合Movable的Car类,但为move(to :)函数提供了自己的实现:
class Car: Movable { func move(to point: CGPoint = CGPoint(x: 0,y: 0)) { print("Moving to point: \(point)") } }
现在我创建了一辆新车并将其作为Movable转发:
let castedCar = Car() as Movable
根据我是否为可选参数点传递值,我观察到两种不同的行为:
>传递可选参数的点时
→汽车的实施被称为:
castedCar.move(to: CGPoint(x: 20,y: 10))
输出:
Moving to point: (20.0,10.0)
>当我调用move()函数而不为可选参数提供值时,将忽略Car的实现
→调用Movable协议的默认实现:
castedCar.move()
输出:
Moving to origin: (0.0,0.0)
为什么?
castedCar.move(to: CGPoint(x: 20,y: 10))
能够被解析为协议要求func move(指向:CGPoint) – 因此调用将通过协议见证表(协议类型实例实现多态的方法)动态调度,允许调用Car的实现.
然而,电话
castedCar.move()
与协议要求func move(指向:CGPoint)不匹配.因此,不会通过协议见证表(仅包含协议要求的方法条目)来调度它.相反,当castedCar被输入为Movable时,编译器将不得不依赖静态分派.因此,将调用协议扩展中的实现.
默认参数值仅仅是函数的静态特性 – 编译器实际上只会发出一个函数的重载(一个包含所有参数).
尝试通过排除其具有默认值的参数之一来应用该函数将简单地触发编译器插入对该默认参数值的评估(因为它可能不是常量),然后将该值插入到调用中.
因此,具有默认参数值的函数不能很好地与动态调度一起使用.使用默认参数值替换方法的类也可以获得意外结果 – 请参阅例如this bug report.
获得默认参数值所需的动态调度的一种方法是简单地在协议中定义静态属性需求,以及协议扩展中的move()重载,该协议扩展只需对其应用move(to :).
protocol Moveable { static var defaultMoveToPoint: CGPoint { get } func move(to point: CGPoint) } extension Moveable { static var defaultMoveToPoint: CGPoint { return .zero } // simply apply move(to:) with our given defined default. // as 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现在是协议要求 – 可以动态调度它,从而为您提供所需的行为.
作为附录,请注意我们在类型(of:self)而不是Self上调用defaultMoveToPoint.这将为我们提供实例的动态元类型值,而不是调用该方法的静态元类型值,从而确保正确调度defaultMoveToPoint.但是,如果调用move()的静态类型(Moveable本身除外)就足够了,可以使用Self.
我将更详细地讨论协议扩展中可用的动态和静态元类型值之间的差异in this Q&A.