Swift协议可以通过向函数和计算属性添加扩展来为其提供默认实现。我已经做了很多次。据我了解, 默认实现仅用作“后备” :当类型符合协议但不提供其自己的实现时,将 执行默认 实现。
至少这就是我阅读《 Swift编程语言》指南的方式:
如果符合类型提供了自己的所需方法或属性的实现,则将使用该实现而不是扩展提供的实现。
现在,我遇到了这样的情况:我的实现某种协议的自定义类型 确实 为特定功能提供了一种实现,但是并未执行-而是执行协议扩展中定义的实现。
例如 ,我定义了一个协议Movable,该协议具有一个功能move(to:)和一个扩展,为该功能提供默认实现:
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:)函数实现的类:
Car
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我观察到两种不同的行为:
point
→ 中Car的实现被称为:
castedCar.move(to: CGPoint(x: 20, y: 10))
输出:
移至点:(20.0,10.0)
move()
→ 该Movable协议的默认实现是不是:
castedCar.move()
移至原点:(0.0,0.0)
这是由于该呼叫
能够满足协议要求func move(to point: CGPoint)-因此,该调用将通过协议见证表(协议类型的值实现多态性的机制)动态分派到,从而允许Car调用的实现。
func move(to point: CGPoint)
但是,电话
不会 不 匹配协议的要求func move(to point: CGPoint)。因此,它不会通过协议见证表(仅包含协议 要求的 方法条目)进行调度。相反,如castedCar键入为Movable,编译器将不得不依赖静态调度。因此,将调用协议扩展中的实现。
castedCar
默认参数值只是函数的静态功能-编译器实际上只会发出函数的单个重载(一个带有 所有 参数的重载)。尝试通过排除具有默认值的参数之一来应用函数,将触发编译器插入该默认参数值的评估值(因为它可能不是恒定的),然后在调用位置插入该值。
因此,具有默认参数值的函数不能很好地与动态分配配合使用。您还可以使用具有默认参数值的类覆盖方法获得意想不到的结果- 例如,参见此错误报告。
获得默认参数值所需的动态调度的一种方法是static在协议中定义属性要求,以及move()协议扩展中的过载(仅适用于此)move(to:)。
static
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
作为附录,请注意,我们调用defaultMoveToPoint上type(of: self),而不是Self。这将为我们提供实例的 动态元 类型值,而不是方法调用时的静态元类型值,从而确保defaultMoveToPoint正确分派。但是,如果move()调用的任何对象的静态类型(Moveable本身除外)就足够了,则可以使用Self。
type(of: self)
Self
Moveable