使用协议中定义的默认参数实现功能 [英] Implementing a function with a default parameter defined in a protocol

查看:109
本文介绍了使用协议中定义的默认参数实现功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

至少这就是我阅读

接下来,我定义一个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)


为什么?

解决方案

这是由于调用的事实

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

可以解决协议要求func move(to point: CGPoint) –因此,将通过协议见证表(协议类型的值实现多态性的机制)将呼叫动态分派到该请求,从而允许Car的实现叫做.

但是,通话

castedCar.move()

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

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

由于这个原因,具有默认参数值的函数在动态分派中根本无法很好地发挥作用.您还可以使用具有默认参数值的类覆盖方法获得意外结果–例如,参见此错误报告.


获得默认参数值所需的动态调度的一种方法是在协议中定义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现在是协议的要求–可以动态分配给它,从而为您提供所需的行为.

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

我在此问答中更详细地研究了协议扩展中可用的动态和静态元类型值之间的差异.在此问答中,.

Swift protocols can provide default implementations for functions and computed properties by adding extensions to them. I've done that plenty of times. It is my understanding that the default implementation is only used as a "fallback": It's executed when a type conforms to the protocol but doesn't provide its own implementation.

At least that's how I read The Swift Programming Language guide:

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.

Now I ran into a situation where my custom type that implements a certain protocol does provide an implementation for a particular function but it's not executed — the implementation defined in the protocol extension is executed instead.


As an example, I define a protocol Movable that has a function move(to:) and an extension that provides a default implementation for this function:

protocol Movable {

    func move(to point: CGPoint)

}

extension Movable {

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

}

Next, I define a Car class that conforms to Movable but provides its own implementation for the move(to:) function:

class Car: Movable {

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

}

Now I create a new Car and downcast it as a Movable:

let castedCar = Car() as Movable

Depending on whether I pass a value for the optional parameter point I observe two distinct behaviors:


  1. When passing a point for the optional parameter

    the Car's implementation is called:

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

    Output:

    Moving to point: (20.0, 10.0)


  1. When I invoke the move() function without providing a value for the optional parameter the Car's implementation is ignored and

    the Movable protocol's default implementation is called instead:

    castedCar.move()
    

    Output:

    Moving to origin: (0.0, 0.0)


Why?

解决方案

This is due to the fact that the call

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

is able to be resolved to the protocol requirement func move(to point: CGPoint) – therefore the call will be dynamically dispatched to via the protocol witness table (the mechanism by which protocol-typed values achieve polymorphism), allowing Car's implementation to be called.

However, the call

castedCar.move()

does not match the protocol requirement func move(to point: CGPoint). It therefore won't be dispatched to via the protocol witness table (which only contains method entries for protocol requirements). Instead, as castedCar is typed as Movable, the compiler will have to rely on static dispatch. Therefore the implementation in the protocol extension will be called.

Default parameter values are merely a static feature of functions – only a single overload of the function will actually be emitted by the compiler (one with all the parameters). Attempting to apply a function by excluding one of its parameters which has a default value will trigger the compiler to insert an evaluation of that default parameter value (as it may not be constant), and then insert that value at the call site.

For that reason, functions with default parameter values simply do not play well with dynamic dispatch. You can also get unexpected results with classes overriding methods with default parameter values – see for example this bug report.


One way to get the dynamic dispatch you want for the default parameter value would be to define a static property requirement in your protocol, along with a move() overload in a protocol extension which simply applies move(to:) with it.

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)

Because defaultMoveToPoint is now a protocol requirement – it can be dynamically dispatched to, thus giving you your desired behaviour.

As an addendum, note that we're calling defaultMoveToPoint on type(of: self) rather than Self. This will give us the dynamic metatype value for the instance, rather than the static metatype value of what the method is called on, ensuring defaultMoveToPoint is dispatched correctly. If however, the static type of whatever move() is called on (with the exception of Moveable itself) is sufficient, you can use Self.

I go into the differences between the dynamic and static metatype values available in protocol extensions in more detail in this Q&A.

这篇关于使用协议中定义的默认参数实现功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆