How to Satisfy Swift Protocol and Add Defaulted Arguments

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.

Swift - Protocol extensions - Property default values

It seems you want to add a stored property to a type via protocol extension. However this is not possible because with extensions you cannot add a stored property.

I can show you a couple of alternatives.

Subclassing (Object Oriented Programming)

The easiest way (as probably you already imagine) is using classes instead of structs.

class IdentifiableBase {
var id = 0
var name = "default"
}

class A: IdentifiableBase { }

let a = A()
a.name = "test"
print(a.name) // test

Cons: In this case your A class needs to inherit from IdentifiableBase and since in Swift theres is not multiple inheritance this will be the only class A will be able to inherit from.

Components (Protocol Oriented Programming)

This technique is pretty popular in game development

struct IdentifiableComponent {
var id = 0
var name = "default"
}

protocol HasIdentifiableComponent {
var identifiableComponent: IdentifiableComponent { get set }
}

protocol Identifiable: HasIdentifiableComponent { }

extension Identifiable {
var id: Int {
get { return identifiableComponent.id }
set { identifiableComponent.id = newValue }
}
var name: String {
get { return identifiableComponent.name }
set { identifiableComponent.name = newValue }
}
}

Now you can make your type conform to Identifiable simply writing

struct A: Identifiable {
var identifiableComponent = IdentifiableComponent()
}

Test

var a = A()
a.identifiableComponent.name = "test"
print(a.identifiableComponent.name) // test

Variadic parameters and defaulted parameters

Although this may seem like a weird work around, it does work, you can use method overloading:

// Calling this will result in using the default value
func sum(#startingValue:Int, values:Int...) -> Int {
return sum(startingValue: startingValue, values);
}

// Calling this will use whatever value you specified
func sum(#startingValue:Int, #additionalValue:Int, values:Int...) -> Int {
return sum(startingValue: startingValue, additionalValue: additionalValue, values);
}

// The real function where you can set your default value
func sum(#startingValue:Int, additionalValue:Int = 77, values:Int[]) -> Int {
var total:Int = startingValue + additionalValue
for v in values {
total += v
}

return total
}

// You can then call it either of these two ways:
// This way uses will use the value 77 for additional value
sum(startingValue:10, 1, 2, 3, 4, 5, 6, 7) // = 115

// This way sets additionalValue to the value of 1
sum(startingValue:10, additionalValue: 1, 2, 3, 4, 5, 6, 7) // = 38

To be honest, I am not entirely sure why your first solution did not work automatically, in the docs I found this:

If your function has one or more parameters with a default value, and
also has a variadic parameter, place the variadic parameter after all
the defaulted parameters at the very end of the list.

But was unable to make it work, maybe a bug? I would guess it is supposed to work the same way I showed you. If you specify additionalValue it will use it, otherwise it will use the default. So maybe it will work automatically in the near future (making this solution irrelevant)?

Original Answer

The solution below works if you solely want to cease using the word additionalValue while calling the function but it still assigns additionalValue an argument (not what the OP was looking for).

Put an underscore in front of additionalValue:

func sum(#startingValue:Int, _ additionalValue:Int = 77, values:Int...) -> Int {
// ...
}

Then you can call it how you want without warnings:

sum(startingValue:10, 1, 2, 3, 4, 5, 6, 7)

In this case additionalValue automatically equals the second parameter, so it would be equal to 1

Why must Protocol defaults be implemented via Extensions in Swift?

The @optional directive is an Objective-C only directive and has not been translated to Swift. This doesn't mean that you can't use it in Swift, but that you have to expose your Swift code to Objective-C first with he @objc attribute.

Note that exposing to Obj-C will only make the protocol available to types that are in both Swift and Obj-C, this excludes for example Structs as they are only available in Swift!

To answer your first question, Protocols are here to define and not implement:

A protocol defines a blueprint of methods, properties, and other requirements [...]

And the implementation should thus be supplied by the class/stuct/enum that conforms to it:

The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements

This definition really applies to Protocols we use in our daily lives as well. Take for example the protocol to write a Paper:

The PaperProtocol defines a paper as a document with the following non-nil variables:

  • Introduction
  • Chapters
  • Conclusion

What the introduction, chapters and conclusion contain are up to the one implementing them (the writer) and not the protocol.

When we look at the definition of Extensions, we can see that they are here to add (implement) new functionalities:

Extensions add new functionality to an existing class, structure, enumeration, or protocol type. This includes the ability to extend types for which you do not have access to the original source code.

So extending a protocol (which is allowed) gives you the possibility to add new functionality and hereby give a default implementation of a defined method. Doing so will work as a Swift only alternative to the @optional directive discussed above.

UPDATE:

While giving a default implementation to a protocol function in Switch does make it "optional", it is fundamentally not the same as using the @optional directive in Objective-C.

In Objective-C, an optional method that is not implemented has no implementation at all so calling it would result in a crash as it does not exist. One would thus have to check if it exists before calling it, in opposition to Swift with an extension default where you can call it safely as a default implementation exists.

An optional in Obj-C would be used like this:

NSString *thisSegmentTitle;
// Verify that the optional method has been implemented
if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {
// Call it
thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];
} else {
// Do something as fallback
}

Where it's Swift counterpart with extension default would be:

let thisSegmentTitle = self.dataSource.titleForSegmentAtIndex(index)

Calling protocol default implementation from regular method

I don't know if you are still looking for an answer to this, but the way to do it is to remove the function from the protocol definition, cast your object to Foo and then call the method on it:

protocol Foo { 
// func testPrint() <- comment this out or remove it
}

extension Foo {
func testPrint() {
print("Protocol extension call")
}
}

struct Bar: Foo {
func testPrint() {
print("Call from struct")
(self as Foo).testPrint() // <- cast to Foo and you'll get the default
// function defined in the extension
}
}

Bar().testPrint()

// Output: "Call from struct"
// "Protocol extension call"

For some reason it only works if the function isn't declared as part of the protocol, but is defined in an extension to the protocol. Go figure. But it does work.

Swift func signature and default parameter values syntax?

The following radar:

  • https://github.com/lionheart/openradar-mirror/issues/12160
  • http://www.openradar.me/18041799

Was marked as fixed in the following validation test:

  • swift/validation-test/compiler_crashers_fixed/00023-getcallerdefaultarg.swift
// Test case submitted to project by https://github.com/practicalswift (practicalswift)
// http://www.openradar.me/18041799
// https://gist.github.com/stigi/336a9851cd80fdef22ed
func a(b: Int = 0) {
}
let c = a
c() // expected-error {{missing argument for parameter #1 in call}}

Based on the validation test above (particularly the expected error), I believe calling a function via a stored reference to it (e.g. L.ws[0] in your example) will not allow omitting arguments even if the function pointed to supplies default parameter values for these omitted arguments.

The type of the c stored closure (/function reference) above will be inferred to as (Int) -> (), and will contain no information regarding default parameter values. When attempting to call c as if it were a closure/function pointer of type () - > (), the missing argument error will be prompted without any possibility of tapping into the compiler magic that works when directly calling a function (not via a stored reference/closure to it!) that, in itself, allows omitting arguments for parameters with default values.



Related Topics



Leave a reply



Submit