Dov
Dov

Reputation: 16166

Is it possible to satisfy Swift protocol and add defaulted arguments?

If you have protocol like so:

protocol Messaging {
    func sendMessage(message: String)
}

Is there any way to satisfy it in a class like so:

class Messager: Messaging {
    func sendMessage(message: String, count: Int = 1) {}
}

This would be nice to have, as the resulting signature of the protocol is satisfied by adding the defaulted parameter. Is there any way to get this to work with Swift 2?

This is a simplified example. Let's say, for the sake of argument, that the protocol is fixed. A solution can only update the Messager class. My goal is to be able to call sendMessage() like so:

let m: Messaging = Messager()
m.sendMessage("")

The only way I found to accomplish this (and satisfy the compiler) is with overloading like so:

class Messager: Messaging {
    func sendMessage(message: String) {
        self.sendMessage(message, count: 1)
    }

    func sendMessage(message: String, count: Int = 1) {}
}

The problem with this approach is that my defaults are then specified in two places and I lose the main advantage of Swift's default parameters.

Upvotes: 65

Views: 36889

Answers (4)

hhamm
hhamm

Reputation: 1581

in Swift 3 you could use extensions to solve that, however it's a bit ugly. Hope for a better solution in next swift versions.

import UIKit

protocol TestProtocol {
    func testFunction(a: Int, b: Int?) -> String
}

extension TestProtocol
{
    func testFunction(a: Int, b: Int? = nil) -> String {
        return testFunction(a: a, b: b)
    }
}

class TestClass: TestProtocol
{
    func testFunction(a: Int, b: Int?) -> String {
        return "a: \(a), b: \(b)"
    }
}

func testit(testProtocol: TestProtocol) {
    print(testProtocol.testFunction(a: 10)) // will print a: 10, b: nil
    print(testProtocol.testFunction(a:10, b:20)) // will print a: 10, b: Optional(20)
}

let t = TestClass()
testit(testProtocol: t)

However, this would somehow lead to one issue. If some class is not conforming to the protocol, it wont result in a compile error but rather in an endless loop.

A slightly better solution (in my opinion) is to capsulate the default parameter in a second function like this:

import Foundation

protocol TestProtocol {
    func testFunction(a: Int, b: Int?) -> String
}

extension TestProtocol
{
    // omit the second parameter here
    func testFunction(a: Int) -> String {
        return testFunction(a: a, b: nil) // <-- and use the default parameter here
    }
}

class TestClass: TestProtocol
{
   func testFunction(a: Int, b: Int?) -> String 
   {
       return "testFunction(a: \(a), b: \(b))"       
   }
}

func testit(testProtocol: TestProtocol) {
    print(testProtocol.testFunction(a: 10)) // will print a: 10, b: nil
    print(testProtocol.testFunction(a: 10, b: 20)) // will print a: 10, b: Optional(20)
}
print(Date())
let t = TestClass()
testit(testProtocol: t)

In this way the compiler will notify when a class does not conform to the protocol, and also wont end up in and endless loop.

Upvotes: 84

crashoverride777
crashoverride777

Reputation: 10674

With Swift 2 you can now extend your protocol like so and give it a default implementation

protocol Messageable {
    func sendMessage(message: String)
}

extension Messageable {
    func sendMessage(message: String, count: Int = 1) {
        // Do your default implementation
    }
}

I am still learning about this so I am not 100% sure how this will work with your send message example, but I believe this is what you are looking for.

There are so many new cool things you can do with protocols in Swift 2.

Watch Apple's presentation which is very good:

https://developer.apple.com/videos/play/wwdc2015-408/

and read these:

http://matthijshollemans.com/2015/07/22/mixins-and-traits-in-swift-2/

http://code.tutsplus.com/tutorials/protocol-oriented-programming-in-swift-2--cms-24979

http://www.raywenderlich.com/109156/introducing-protocol-oriented-programming-in-swift-2

Upvotes: 12

John C.
John C.

Reputation: 451

If anyone is still looking for an answer to this, this link helped me out:

https://oleb.net/blog/2016/05/default-arguments-in-protocols/

Basically the original function definition includes all the params you need:

protocol Messaging {
    func sendMessage(message: String, count: Int)
}

and your extension provides the default values, calling your original protocol function with those default values:

extension Messaging {
    func sendMessage(message: String, count: Int = 1) {
        sendMessage(message, count)
    }
}

Upvotes: 34

dfrib
dfrib

Reputation: 73186

You could add typealiases to your protocol to represent possible different argument types in your protocol-blueprinted function .sendMessage. In the example below I have explicitly specified that the 2nd argument has neither an internal nor an external name.

Since you have two typealiases, you can either implement this blueprint as one using two different types (Messenger in example below), or, simply throw away the second argument (AnotherMessenger in example) as an empty tuple type () with default value () (you can think of this as a void type with a value of void).

protocol Messaging {
    typealias T
    typealias U
    func sendMessage(message: T, _ _ : U)
}

/* Class where you make use of 2nd argument */
class Messager: Messaging {

    func sendMessage(message: String, _ count: Int) {
        print(message + "\(count)")
    }
}

/* Class where you ignore 2nd argument */
class AnotherMessager : Messaging {

    func sendMessage(message: String, _ _ : () = ()) {
        print(message)
    }
}

/* Tests */
var a = Messager()
a.sendMessage("Hello world #", 1)
// prints "Hello World #1"

var b = AnotherMessager()
b.sendMessage("Hello world")
// prints "Hello World"

This is as close as I can get you to simulate the behaviour you describe in the question and in the comments below. I'll leave the alternative solutions below in case they can help someone else with similar thought but less hard design constraints.


Another option: not exactly the behaviour your asking for, but you could use an anonymous closure as the single parameter to your protocol-blueprinted function, where this close take no arguments but returns an array of Any objects, which you can access and treat in your sendMessage function as you wish.

protocol Messaging {
    func sendMessage(@autoclosure messages: ()->[Any])
}

class Messager: Messaging {
    func sendMessage(@autoclosure messages: ()->[Any]) {
        for message in messages() {
            print(message, terminator: "")
        }
    }
}

var a = Messager()
a.sendMessage([String("Hello "), String("World "), String("Number "), Int(1)])
// prints "Hello World Number 1"

Another alternative would be to have to separate blueprints of function sendMessage(..) in your protocol Messaging, one with and one without the additional parameter count. You thereafter add default (dummy) implementations for both of these functions via extension of protocol Messaging. Your class Messager will then comply to Messaging protocol even without any implementation of sendMessage(..) in it at all; in the lack thereof, the default implementations are used. Finally make a detailed implementation only the sendMessage function you wish to use in your class.

protocol Messaging {
    func sendMessage(message: String)
    func sendMessage(message: String, count: Int)
}

/* Extend blueprints with default dummy implementations */
extension Messaging {
    func sendMessage(message: String) { }
    func sendMessage(message: String, count: Int = 1) { }
}

class Messager: Messaging {
    func sendMessage(message: String, count: Int = 1) {
        print(message + "\(count)")
    }
}

var a = Messager()
a.sendMessage("Hello world #")
// prints "Hello World #1"

Note however that instances of your class will list both sendMessage functions as available class methods; one of them being your function, and the other the dummy default implementation.


Old answer prior to edit regarding different type parameters (I'll leave it here as it can be a possible alternative in the case where all parameters are of same type)

Finally, you could make use of variadic parameters:

protocol Messaging {
    func sendMessage(messages: String...)
}

class Messager: Messaging {
    func sendMessage(messages: String...) {
        for message in messages {
            print(message)
        }
    }
}

var a = Messager()

a.sendMessage("Hello", "World", "!")

A variadic parameter accepts zero or more values of a specified type. You use a variadic parameter to specify that the parameter can be passed a varying number of input values when the function is called. Write variadic parameters by inserting three period characters (...) after the parameter’s type name.

Upvotes: 6

Related Questions