Implementing a Function with a Default Parameter Defined in a Protocol

Implementing a function with a default parameter defined in a protocol

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.

Error while using default value in parameter of Protocol's method

I would suggest you to use following method signature:

extension TestP {
func update(srId: String) {
update(srId: srId, srType: "")
}
}

Because if you want to pass srType in the call you already have the method func update(srId: String, srType: String?) in the protocol.

There is no need to use parameter with default value.

Call with default value:

let test: TestP = Test()
test.update(srId: "abc")

Call with srType parameter:

let test: TestP = Test()
test.update(srId: "abc", srType: "type")

Extension of protocol with method which adds default parameter

This is happening because compiler is confuse due to ambiguous functions.

  1. Here SomeClassDatabaseController receiving count() method from two different protocols.

  2. DatabaseControllerProtocol has count(forPredicate) method which always need parameter.

  3. On other hand SomeSpecificDatabaseControllerProtocol have count() method which can have empty parameter.

  4. To solve this either you have to change count method in DatabaseControllerProtocol to this or you have to implement it in SomeClassDatabaseController.

func count(forPredicate predicate: NSPredicate? = nil) -> Int { return 0}

Reasons to include function in protocol definition vs. only defining it in the extension?

Declaring the function as part of the protocol definition instructs the compiler to use dynamic dispatch when calling the function, as the compiler would expect types implementing the protocol to give an implementation for that function. This is called a method requirement. Now, if the type doesn't define the method, then the runtime resolves the method call to the method declared in the protocol extension.

However, declaring the function in the protocol extension only tells the compiler that he doesn't need to use the dynamic dispatch, and instead it uses the static dispatch, which is faster, but doesn't work very well with polymorphism, as the protocol extension implementation will be called even if the types conforming to the protocol also implement the method.

To exemplify the above, let's consider the following code:

protocol Shape {
func draw()
}

extension Shape {
func draw(){
print("This is a Shape")
}
}

struct Circle: Shape {
func draw() {
print("This is a Circle")
}
}

struct Square: Shape {
func draw() {
print("This is a Square")
}
}

let shapes: [Shape] = [Circle(), Square()]

for shape in shapes {
shape.draw()
}

The above code will have the output

This is a Circle 
This is a Square

This is because draw() is a method requirement, meaning that when draw() is invoked, the runtime will search for the implementation of draw () within the actual type of the element, in this case within Circle and Square.

Now if we don't declare draw as a method requirement, meaning we don't mention it within the protocol declaration

protocol Shape {
}

Then the compiler will no longer use the dynamic dispatch, and will go straight to the implementation defined in the protocol extension. Thus the code will print:

This is a Shape
This is a Shape

More, if we down cast cast an element of the array to the type we expect it would be, then we get the overloaded behaviour. This will print This is a Circle

if let circle = shapes[0] as? Circle {
circle.draw()
}

because the compiler is now able to tell that the first element of shapes is a Circle, and since Circle has a draw() method it will call that one.

This is Swift's way to cope with abstract classes: it gives you a way you to specify what you expect from types conforming to that protocol, while allowing for default implementations of those methods.

How can I extend a Swift protocol with a default parameter when using generic types?

Is there any way I can specify that by default Query.Data = Output

No.

But, you can define an overload which works only on Query.Data = Output:

extension DataFetcher {
func fetch<Query: QueryProtocol, Output>(
query: Query,
completionHandler: (Output) -> Void
)
where Query.Data == Output
{
fetch(query: query, parser: { $0 }, completionHandler: completionHandler)
}
}


Related Topics



Leave a reply



Submit