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

SwiftSwift2

Swift Problem Overview


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.

Swift Solutions


Solution 1 - Swift

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.

Solution 2 - Swift

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)
    }
}

Solution 3 - Swift

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

Solution 4 - Swift

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDovView Question on Stackoverflow
Solution 1 - SwifthhammView Answer on Stackoverflow
Solution 2 - SwiftJohn C.View Answer on Stackoverflow
Solution 3 - Swiftcrashoverride777View Answer on Stackoverflow
Solution 4 - SwiftdfribView Answer on Stackoverflow