web-dev-qa-db-fra.com

Est-il possible de satisfaire le protocole Swift et d'ajouter des arguments par défaut?

Si vous avez un protocole comme celui-ci:

protocol Messaging {
    func sendMessage(message: String)
}

Existe-t-il un moyen de le satisfaire dans une classe comme celle-ci:

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

Ce serait bien d'avoir, car la signature résultante du protocole est satisfaite en ajoutant le paramètre par défaut. Existe-t-il un moyen de faire fonctionner cela avec Swift 2?

Ceci est un exemple simplifié. Disons, pour les besoins de l'argument, que le protocole est fixe. Une solution ne peut que mettre à jour la classe Messager. Mon objectif est de pouvoir appeler sendMessage() comme ceci:

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

Le seul moyen que j'ai trouvé pour accomplir cela (et satisfaire le compilateur) est de surcharger comme ceci:

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

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

Le problème avec cette approche est que mes valeurs par défaut sont ensuite spécifiées à deux endroits et je perds le principal avantage des paramètres par défaut de Swift.

37
Dov

dans Swift vous pouvez utiliser des extensions pour résoudre cela, mais c'est un peu moche. J'espère une meilleure solution dans les prochaines versions Swift.

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)
48
hhamm

Si quelqu'un cherche toujours une réponse à cette question, ce lien m'a aidé:

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

Fondamentalement, la définition de fonction d'origine comprend tous les paramètres dont vous avez besoin:

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

et votre extension fournit les valeurs par défaut, appelant votre fonction de protocole d'origine avec ces valeurs par défaut:

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

Avec Swift 2 vous pouvez maintenant étendre votre protocole comme ça et lui donner une implémentation par défaut

protocol Messageable {
    func sendMessage(message: String)
}

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

J'apprends toujours à ce sujet, donc je ne suis pas sûr à 100% de la façon dont cela fonctionnera avec votre exemple de message d'envoi, mais je crois que c'est ce que vous recherchez.

Il y a tellement de nouvelles choses intéressantes que vous pouvez faire avec les protocoles dans Swift 2.

Regardez la présentation d'Apple qui est très bonne:

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

et lisez-les:

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

12
crashoverride777

Vous pouvez ajouter des typologies à votre protocole pour représenter différents types d'arguments possibles dans votre fonction de protocole .sendMessage. Dans l'exemple ci-dessous, j'ai explicitement spécifié que le 2ème argument n'a ni nom interne ni nom externe.

Étant donné que vous avez deux types de typographie, vous pouvez soit implémenter ce plan comme un en utilisant deux types différents (Messenger dans l'exemple ci-dessous), soit simplement jeter le deuxième argument (AnotherMessenger dans l'exemple) comme un vide Type de tuple () avec la valeur par défaut () (vous pouvez le considérer comme un type void avec une valeur 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"

C'est aussi proche que possible de vous faire simuler le comportement que vous décrivez dans la question et dans les commentaires ci-dessous. Je laisse les solutions alternatives ci-dessous au cas où elles pourraient aider quelqu'un d'autre avec une pensée similaire mais des contraintes de conception moins dures.


Une autre option: pas exactement le comportement que vous demandez, mais vous pouvez utiliser une fermeture anonyme comme paramètre unique de votre fonction de protocole, où cette fermeture ne prend aucun argument mais renvoie un tableau d'objets Any, que vous peut accéder et traiter votre fonction sendMessage comme vous le souhaitez.

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"

Une autre alternative serait d'avoir à séparer les plans de la fonction sendMessage(..) dans votre protocole Messaging, une avec et une sans le paramètre supplémentaire count. Vous ajoutez ensuite des implémentations (factices) par défaut pour les deux de ces fonctions via l'extension du protocole Messaging. Votre classe Messager se conformera alors au protocole Messaging même sans aucune implémentation de sendMessage(..) dedans; à défaut, les implémentations par défaut sont utilisées. Enfin, effectuez une implémentation détaillée uniquement la fonction sendMessage que vous souhaitez utiliser dans votre classe.

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"

Notez cependant que les instances de votre classe répertorieront les deux fonctions sendMessage comme méthodes de classe disponibles; l'un d'eux étant votre fonction, et l'autre l'implémentation factice par défaut.


Ancienne réponse avant modification concernant les différents paramètres de type (je vais la laisser ici car elle peut être une alternative possible dans le cas où tous les paramètres sont du même type)

Enfin, vous pouvez utiliser paramètres variadiques :

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", "!")

Un paramètre variadic accepte zéro ou plusieurs valeurs d'un type spécifié. Vous utilisez un paramètre variadic pour spécifier que le paramètre peut recevoir un nombre variable de valeurs d'entrée lorsque la fonction est appelée. Écrivez des paramètres variadiques en insérant trois caractères de période (...) après le nom du type du paramètre.

4
dfri