Comment définir des méthodes optionnelles dans le protocole Swift?

359

Est-ce possible dans Swift? Sinon, existe-t-il une solution de contournement pour le faire?

Selvin
la source
1
Je me suis finalement motivé à mettre à jour ma réponse, vous pouvez reconsidérer son acceptation.
akashivskyy
@akashivskyy J'accepte votre réponse car elle présente mieux toutes les options disponibles et leurs avantages et inconvénients.
Selvin

Réponses:

506

1. Utilisation d'implémentations par défaut (de préférence).

protocol MyProtocol {
    func doSomething()
}

extension MyProtocol {
    func doSomething() {
        /* return a default value or just leave empty */
    }
}

struct MyStruct: MyProtocol {
    /* no compile error */
}

Les avantages

  • Aucun runtime Objective-C n'est impliqué (enfin, pas explicitement au moins). Cela signifie que vous pouvez lui conformer des structures, des énumérations et des non- NSObjectclasses. En outre, cela signifie que vous pouvez profiter d'un puissant système générique.

  • Vous pouvez toujours être sûr que toutes les exigences sont remplies lorsque vous rencontrez des types conformes à ce protocole. C'est toujours une implémentation concrète ou une implémentation par défaut. C'est ainsi que les "interfaces" ou les "contrats" se comportent dans d'autres langues.

Désavantages

  • Pour les non- Voidexigences, vous devez avoir une valeur par défaut raisonnable , ce qui n'est pas toujours possible. Cependant, lorsque vous rencontrez ce problème, cela signifie que cette exigence ne devrait vraiment pas avoir d'implémentation par défaut, ou que vous vous êtes trompé lors de la conception de l'API.

  • Vous ne pouvez pas faire la distinction entre une implémentation par défaut et aucune implémentation , du moins sans résoudre ce problème avec des valeurs de retour spéciales. Prenons l'exemple suivant:

    protocol SomeParserDelegate {
        func validate(value: Any) -> Bool
    }
    

    Si vous fournissez une implémentation par défaut qui revient juste true- c'est bien au premier coup d'œil. Maintenant, considérez le pseudo-code suivant:

    final class SomeParser {
        func parse(data: Data) -> [Any] {
            if /* delegate.validate(value:) is not implemented */ {
                /* parse very fast without validating */
            } else {
                /* parse and validate every value */
            }
        }
    }
    

    Il n'y a aucun moyen d'implémenter une telle optimisation - vous ne pouvez pas savoir si votre délégué implémente une méthode ou non.

    Bien qu'il existe un certain nombre de façons différentes de résoudre ce problème (en utilisant des fermetures facultatives, différents objets délégués pour différentes opérations pour n'en nommer que quelques-uns), cet exemple présente clairement le problème.


2. Utilisation @objc optional.

@objc protocol MyProtocol {
    @objc optional func doSomething()
}

class MyClass: NSObject, MyProtocol {
    /* no compile error */
}

Les avantages

  • Aucune implémentation par défaut n'est nécessaire. Vous venez de déclarer une méthode facultative ou une variable et vous êtes prêt à partir.

Désavantages

  • Il limite considérablement les capacités de votre protocole en exigeant que tous les types conformes soient compatibles avec Objective-C. Cela signifie que seules les classes qui héritent de NSObjectpeuvent se conformer à ce protocole. Pas de structures, pas d'énumérations, pas de types associés.

  • Vous devez toujours vérifier si une méthode facultative est implémentée en appelant ou en vérifiant éventuellement si le type conforme l'implémente. Cela peut introduire beaucoup de passe-partout si vous appelez souvent des méthodes facultatives.

akashivskyy
la source
17
Regardez une méthode améliorée de méthodes optionnelles dans swift en utilisant une extension (ci-dessous)
Daniel Kanaan
1
Et comment tester la prise en charge d'une méthode de protocole facultative dans une instance? respondsToSelector?
devios1
3
Ne fais pas ça! Swift ne prend pas en charge la méthode facultative dans les protocoles pour une raison.
fpg1503
2
Cette méthode ne prend pas en charge les options dans les paramètres. C'est-à-dire que vous ne pouvez pas le faireoptional func doSomething(param: Int?)
SoftDesigner
4
Certes, cette réponse est essentiellement fausse de nos jours, des années plus tard. Aujourd'hui, dans Swift, vous ajoutez simplement une extension pour l'implémentation par défaut, c'est un aspect de base de Swift. (Comme le montrent toutes les réponses modernes ci-dessous.) Il serait tout simplement faux d'ajouter le drapeau objc, de nos jours.
Fattie
394

Dans Swift 2 et versions ultérieures, il est possible d'ajouter des implémentations par défaut d'un protocole. Cela crée une nouvelle façon de méthodes facultatives dans les protocoles.

protocol MyProtocol {
    func doSomethingNonOptionalMethod()
    func doSomethingOptionalMethod()
}

extension MyProtocol {
    func doSomethingOptionalMethod(){ 
        // leaving this empty 
    }
}

Ce n'est pas une très bonne façon de créer des méthodes de protocole facultatives, mais vous donne la possibilité d'utiliser des structures dans des rappels de protocole.

J'ai écrit un petit résumé ici: https://www.avanderlee.com/swift-2-0/optional-protocol-methods/

Antoine
la source
2
C'est probablement la façon la plus propre de le faire dans Swift. Dommage que cela ne fonctionne pas avant Swift 2.0.
Entalpi
13
@MattQuiros Je trouve que vous avez en fait besoin de déclarer la fonction dans la définition du protocole, sinon la fonction d'extension no-op n'est pas remplacée dans vos classes conformes au protocole.
Ian Pearce
3
@IanPearce est correct, et cela semble être par conception. Dans le discours sur la "programmation orientée protocole" (408) à la WWDC, ils parlent des méthodes du protocole principal qui sont des "points de personnalisation" offerts aux types conformes. Un point de personnalisation requis ne reçoit pas de définition dans une extension; un point facultatif le fait. Les méthodes sur le protocole qui ne devraient généralement pas être personnalisées sont entièrement déclarées / définies dans l'extension pour interdire aux types conformes de personnaliser, sauf si vous effectuez une conversion spécifique vers son dynamicType pour montrer que vous souhaitez l'implémentation personnalisée du conformateur.
matthias
4
@FranklinYu Vous pouvez le faire, mais vous modifiez ensuite la conception de votre API avec des «lancers» là où ils ne sont en fait pas nécessaires. J'aime plus l'idée de "micro protocoles". Par exemple, chaque méthode confirme un protocole et vous pouvez ensuite vérifier: si l'objet est un protocole
Darko
3
@Antoine Je pense que vous devriez rendre la fonction dans l'extension publique, car les fonctions dans les protocoles sont publiques par définition. Votre solution ne fonctionnera pas lorsque le protocole sera utilisé en dehors de son module.
Vadim Eisenberg du
39

Puisqu'il y a quelques réponses sur la façon d'utiliser le modificateur facultatif et l' attribut @objc pour définir le protocole d'exigence facultatif, je vais donner un exemple sur la façon d'utiliser les extensions de protocole pour définir le protocole facultatif.

Le code ci-dessous est Swift 3. *.

/// Protocol has empty default implementation of the following methods making them optional to implement:
/// `cancel()`
protocol Cancelable {

    /// default implementation is empty.
    func cancel()
}

extension Cancelable {

    func cancel() {}
}

class Plane: Cancelable {
  //Since cancel() have default implementation, that is optional to class Plane
}

let plane = Plane()
plane.cancel()
// Print out *United Airlines can't cancelable*

Veuillez noter que les méthodes d'extension de protocole ne peuvent pas être invoquées par le code Objective-C, et pire encore, l'équipe Swift ne le réparera pas. https://bugs.swift.org/browse/SR-492

Eddie.Dou
la source
1
Bon travail! Cela devrait être la bonne réponse car elle résout le problème sans impliquer le runtime Objective-C.
Lukas
vraiment sympa!
tania_S
from swift documentation - "Les exigences de protocole avec les implémentations par défaut fournies par les extensions sont distinctes des exigences de protocole facultatives. Bien que les types conformes n'aient pas à fournir leur propre implémentation non plus, les exigences avec les implémentations par défaut peuvent être appelées sans chaînage facultatif."
Mec Os
34

Les autres réponses ici impliquant le marquage du protocole comme "@objc" ne fonctionnent pas lors de l'utilisation de types spécifiques à Swift.

struct Info {
    var height: Int
    var weight: Int
} 

@objc protocol Health {
    func isInfoHealthy(info: Info) -> Bool
} 
//Error "Method cannot be marked @objc because the type of the parameter cannot be represented in Objective-C"

Afin de déclarer des protocoles facultatifs qui fonctionnent bien avec swift, déclarez les fonctions en tant que variables au lieu de func.

protocol Health {
    var isInfoHealthy: (Info) -> (Bool)? { get set }
}

Et puis implémentez le protocole comme suit

class Human: Health {
    var isInfoHealthy: (Info) -> (Bool)? = { info in
        if info.weight < 200 && info.height > 72 {
            return true
        }
        return false
    }
    //Or leave out the implementation and declare it as:  
    //var isInfoHealthy: (Info) -> (Bool)?
}

Vous pouvez ensuite utiliser "?" vérifier si la fonction a été implémentée ou non

func returnEntity() -> Health {
    return Human()
}

var anEntity: Health = returnEntity()

var isHealthy = anEntity.isInfoHealthy(Info(height: 75, weight: 150))? 
//"isHealthy" is true
Zag
la source
3
Avec cette solution, vous ne pouvez pas accéder à vous-même depuis l'une des fonctions de protocole. Cela peut entraîner des problèmes dans certains cas!
George Green
1
@GeorgeGreen Vous pouvez accéder à vous-même. Marquez la variable de fonction comme paresseuse et utilisez une liste de capture à l'intérieur de la fermeture .
Zag
Seuls les classes, protocoles, méthodes et propriétés peuvent utiliser @objc. Si vous utilisez un paramètre Enum dans la définition de la méthode du protocole @ objc, vous êtes condamné.
khunshan
1
@khunshan, cette méthode ne nécessite rien d'être marqué @ objc, à quoi faites-vous référence?
Zag
il s'agit d'une information sur le sujet qui ne peut pas être utilisée comme énumération entre swift et objc et les autres instructions peuvent être pontées avec le mot clé @objc.
khunshan
34

Voici un exemple concret avec le modèle de délégation.

Configurez votre protocole:

@objc protocol MyProtocol:class
{
    func requiredMethod()
    optional func optionalMethod()
}

class MyClass: NSObject
{
    weak var delegate:MyProtocol?

    func callDelegate()
    {
        delegate?.requiredMethod()
        delegate?.optionalMethod?()
    }
}

Définissez le délégué sur une classe et implémentez le protocole. Vérifiez que la méthode facultative n'a pas besoin d'être implémentée.

class AnotherClass: NSObject, MyProtocol
{
    init()
    {
        super.init()

        let myInstance = MyClass()
        myInstance.delegate = self
    }

    func requiredMethod()
    {
    }
}

Une chose importante est que la méthode facultative est facultative et nécessite un "?" lors de l'appel. Mentionnez le deuxième point d'interrogation.

delegate?.optionalMethod?()
Raphael
la source
2
Ce devrait être la bonne réponse. Simple, propre et explicatif.
Echelon
je suis d'accord, cette réponse est courte, douce et directe. Merci!
LuAndre
31

Dans Swift 3.0

@objc protocol CounterDataSource {
    @objc optional func increment(forCount count: Int) -> Int
    @objc optional var fixedIncrement: Int { get }
}

Cela vous fera gagner du temps.

Gautam Sareriya
la source
6
Une source de pourquoi @objcest-elle nécessaire pour tous les membres maintenant?
DevAndArtist
@Gautam Sareriya: Selon vous, quelle est la meilleure approche ou créer des méthodes d'extension vides?
éoniste du
J'ai essayé avec vos méthodes avec requiredflag, mais avec des erreurs: requiredne peut être utilisé que sur les déclarations 'init'.
Ben
27
  • Vous devez ajouter un optionalmot clé avant chaque méthode.
  • Veuillez noter, cependant, que pour que cela fonctionne, votre protocole doit être marqué avec l'attribut @objc.
  • Cela implique en outre que ce protocole serait applicable aux classes mais pas aux structures.
Mehul Parmar
la source
Correction mineure (trop petite pour être éditée!) Mais elle devrait être «facultative» et non «@optional»
Ali Beadle
5
Cela vous oblige également à marquer votre classe implémentant le protocole comme @objc, pas seulement le protocole.
Johnathon Sullinger
13

Une approche Swift pure avec héritage de protocole:

//Required methods
protocol MyProtocol {
    func foo()
}

//Optional methods
protocol MyExtendedProtocol: MyProtocol {
    func bar()
}

class MyClass {
    var delegate: MyProtocol
    func myMethod() {
        (delegate as? MyExtendedProtocol).bar()
    }
}
guenis
la source
11

Pour illustrer la mécanique de la réponse d'Antoine:

protocol SomeProtocol {
    func aMethod()
}

extension SomeProtocol {
    func aMethod() {
        print("extensionImplementation")
    }
}

class protocolImplementingObject: SomeProtocol {

}

class protocolImplementingMethodOverridingObject: SomeProtocol {
    func aMethod() {
        print("classImplementation")
    }
}

let noOverride = protocolImplementingObject()
let override = protocolImplementingMethodOverridingObject()

noOverride.aMethod() //prints "extensionImplementation"
override.aMethod() //prints "classImplementation"
eLillie
la source
8

Je pense qu'avant de demander comment vous pouvez implémenter une méthode de protocole facultative, vous devriez vous demander pourquoi vous devriez en implémenter une.

Si nous considérons les protocoles rapides comme une interface dans la programmation orientée objet classique, les méthodes facultatives n'ont pas beaucoup de sens, et peut-être une meilleure solution serait de créer une implémentation par défaut, ou de séparer le protocole en un ensemble de protocoles (peut-être avec certaines relations d'héritage entre eux) pour représenter la combinaison possible de méthodes dans le protocole.

Pour plus de lecture, voir https://useyourloaf.com/blog/swift-optional-protocol-methods/ , qui donne un excellent aperçu de cette question.

emem
la source
Cette. C'est l'adhésion au principe «I» des principes de développement logiciel SOLID .
Eric
7

Légèrement éloigné du sujet de la question d'origine, mais il se fonde sur l'idée d'Antoine et j'ai pensé que cela pourrait aider quelqu'un.

Vous pouvez également rendre les propriétés calculées facultatives pour les structures avec des extensions de protocole.

Vous pouvez rendre une propriété sur le protocole facultative

protocol SomeProtocol {
    var required: String { get }
    var optional: String? { get }
}

Implémenter la propriété calculée factice dans l'extension de protocole

extension SomeProtocol {
    var optional: String? { return nil }
}

Et maintenant, vous pouvez utiliser des structures qui implémentent ou non la propriété facultative

struct ConformsWithoutOptional {
    let required: String
}

struct ConformsWithOptional {
    let required: String
    let optional: String?
}

J'ai également écrit comment faire des propriétés facultatives dans les protocoles Swift sur mon blog , que je garderai à jour au cas où les choses changeraient avec les versions de Swift 2.

matthewpalmer
la source
5

Comment créer des méthodes de délégué facultatives et obligatoires.

@objc protocol InterViewDelegate:class {

    @objc optional func optfunc()  //    This is optional
    func requiredfunc()//     This is required 

}
Saurabh Sharma
la source
3

Voici un exemple très simple pour les classes rapides UNIQUEMENT, et non pour les structures ou les énumérations. Notez que la méthode de protocole étant facultative, a deux niveaux de chaînage facultatif en jeu. La classe adoptant le protocole a également besoin de l'attribut @objc dans sa déclaration.

@objc protocol CollectionOfDataDelegate{
   optional func indexDidChange(index: Int)
}


@objc class RootView: CollectionOfDataDelegate{

    var data = CollectionOfData()

   init(){
      data.delegate = self
      data.indexIsNow()
   }

  func indexDidChange(index: Int) {
      println("The index is currently: \(index)")
  }

}

class CollectionOfData{
    var index : Int?
    weak var delegate : CollectionOfDataDelegate?

   func indexIsNow(){
      index = 23
      delegate?.indexDidChange?(index!)
    }

 }
Bénédiction Lopes
la source
Pouvez-vous décrire un peu plus la partie " deux niveaux d'option en jeu ", à savoir delegate?.indexDidChange?(index!):?
Unheilig
2
Si nous avions écrit le protocole pour avoir une méthode non optionnelle comme celle-ci: protocol CollectionOfDataDelegate{ func indexDidChange(index: Int) } alors vous l'appelleriez sans le point d'interrogation: delegate?.indexDidChange(index!) lorsque vous définissez une exigence facultative pour une méthode dans un protocole, le type qui s'y conformera pourrait ne PAS implémenter cette méthode , donc le ?est utilisé pour vérifier l'implémentation et s'il n'y en a pas, le programme ne plantera pas. @Unheilig
Blessing Lopes
weak var delegate : CollectionOfDataDelegate?(assurer une référence faible?)
Alfie Hanssen
@BlessingLopes Pouvez-vous ajouter votre explication de l' delegate?utilisation à votre réponse? Cette information devrait vraiment appartenir à d'autres à l'avenir. Je veux voter positivement, mais cette information devrait vraiment être dans la réponse.
Johnathon Sullinger
3

si vous voulez le faire en pure swift, le mieux est de fournir une implémentation par défaut si vous retournez un type Swift comme par exemple struct avec des types Swift

exemple :

struct magicDatas {
    var damagePoints : Int?
    var manaPoints : Int?
}

protocol magicCastDelegate {
    func castFire() -> magicDatas
    func castIce() -> magicDatas
}

extension magicCastDelegate {
    func castFire() -> magicDatas {
        return magicDatas()
    }

    func castIce() -> magicDatas {
        return magicDatas()
    }
}

alors vous pouvez implémenter un protocole sans définir chaque fonction

p1ckl3
la source
2

Il existe deux façons de créer une méthode facultative dans le protocole rapide.

1 - La première option consiste à marquer votre protocole à l'aide de l'attribut @objc. Bien que cela signifie qu'il ne peut être adopté que par des classes, cela signifie que vous marquez les méthodes individuelles comme étant facultatives comme ceci:

@objc protocol MyProtocol {
    @objc optional func optionalMethod()
}

2 - Une manière plus rapide: cette option est meilleure. Écrivez des implémentations par défaut des méthodes facultatives qui ne font rien, comme ceci.

protocol MyProtocol {
    func optionalMethod()
    func notOptionalMethod()
}

extension MyProtocol {

    func optionalMethod() {
        //this is a empty implementation to allow this method to be optional
    }
}

Swift a une fonctionnalité appelée extension qui nous permet de fournir une implémentation par défaut pour les méthodes que nous voulons être facultatives.

Himanshu
la source
0

Une option consiste à les stocker en tant que variables de fonction facultatives:

struct MyAwesomeStruct {
    var myWonderfulFunction : Optional<(Int) -> Int> = nil
}

let squareCalculator =
    MyAwesomeStruct(myWonderfulFunction: { input in return input * input })
let thisShouldBeFour = squareCalculator.myWonderfulFunction!(2)
FrankByte.com
la source
-1

Définissez la fonction dans le protocole et créez l'extension pour ce protocole, puis créez une implémentation vide pour la fonction que vous souhaitez utiliser comme facultatif.

Rishu Gupta
la source
-2

Pour définir Optional Protocoldans swift, vous devez utiliser le @objcmot-clé before Protocoldeclaration et attribute/ methoddeclaration dans ce protocole. Voici un exemple de propriété facultative d'un protocole.

@objc protocol Protocol {

  @objc optional var name:String?

}

class MyClass: Protocol {

   // No error

}
Ankur Gupta
la source
2
Bien que cela puisse répondre à la question, il est préférable d'ajouter une description de la façon dont cette réponse peut aider à résoudre le problème. Veuillez lire Comment écrire une bonne réponse pour en savoir plus.
Roshana Pitigala
-23

Mettez le @optionaldevant des méthodes ou des propriétés.

shucao
la source
Erreur du compilateur: l'attribut «facultatif» ne peut être appliqué qu'aux membres d'un protocole @objc.
Selvin
3
@optionaln'est même pas le bon mot-clé. C'est le cas optional, et vous devez déclarer la classe et le protocole avec l' @objcattribut.
Johnathon Sullinger