web-dev-qa-db-fra.com

Swift: vérifie si le type générique est conforme au protocole

J'ai un protocole que j'ai défini comme suit:

protocol MyProtocol {
   ...
}

J'ai aussi une structure générique:

struct MyStruct <T>  {
    ...
}

Enfin, j'ai une fonction générique:

func myFunc <T> (s: MyStruct<T>) -> T? {
   ...
}

Je voudrais tester à l'intérieur de la fonction si le type T est conforme à MyProtocol. En gros, j'aimerais pouvoir faire (~ pseudocode):

let conforms = T.self is MyProtocol

Mais cela jette une erreur de compilation:

error: cannot downcast from 'T.Type' to non-@objc protocol type 'MyProtocol'
   let conforms = T.self is MyProtocol
                  ~~~~~~ ^  ~~~~~~~~~~

J'ai aussi essayé des variantes, comme T.self is MyProtocol.self, T is MyProtocol, et en utilisant == au lieu de is. Jusqu'à présent, je ne suis nulle part. Des idées?

61
Alex

Un peu tard mais vous pouvez tester si quelque chose répond au protocole avec as ? test:

if let currentVC = myViewController as? MyCustomProtocol {
    // currentVC responds to the MyCustomProtocol protocol =]
}

EDIT: un peu plus court:

if let _ = self as? MyProtocol {
    // match
}

Et en utilisant un garde:

guard let _ = self as? MyProtocol else {
    // doesn't match
    return
}
60
jlngdt

Je dois dire que @Alex veut vérifier si le type T est conforme au protocole plutôt que s. Et un répondant n'a pas vu clairement.

Vérifiez que le type T est conforme au protocole suivant:

if let _ = T.self as? MyProtocol.Type {
    //  T conform MyProtocol
}

ou

if T.self is MyProtocol.Type {
    //  T conform MyProtocol
}
57
maquannene

La réponse la plus simple est: ne faites pas ça. Utilisez plutôt la surcharge et les contraintes, et déterminez tout au début lors de la compilation plutôt que de tester de manière dynamique les tâches au moment de l'exécution. La vérification des types à l'exécution et les génériques au moment de la compilation ressemblent au steak et à la crème glacée. Ils sont tous les deux agréables, mais les mélanger est un peu bizarre.

Considérons quelque chose comme ceci:

protocol MyProtocol { }

struct MyStruct <T>  { let val: T }

func myFunc<T: MyProtocol>(s: MyStruct<T>) -> T? {
    return s.val
}

func myFunc<T>(s: MyStruct<T>) -> T? {
    return nil
}

struct S1: MyProtocol { }
struct S2 { }

let m1 = MyStruct(val: S1())
let m2 = MyStruct(val: S2())

myFunc(m1) // returns an instance of S1
myFunc(m2) // returns nil, because S2 doesn't implement MyProtocol

L’inconvénient étant que vous ne pouvez pas établir de manière dynamique si T prend en charge un protocole au moment de l’exécution:

let o: Any = S1()
let m3 = MyStruct(val: o)
myFunc(m3)  // will return nil even though o 
            // does actually implement MyProtocol

Mais, en toute honnêteté, avez-vous vraiment besoin de faire cela dans votre fonction générique? Si vous n’êtes pas sûr du type réel, la meilleure solution consiste peut-être à le définir immédiatement, plutôt que de le reporter à plus tard et de le pousser dans une fonction générique pour le découvrir.

31
Airspeed Velocity

vous pouvez également utiliser Swift correspondance de modèle de casse , si vous voulez gérer plusieurs cas de type T:

func myFunc<T>(s: MyStruct<T>) -> T? {
    switch s {
    case let sType as MyProtocol:
        // do MyProtocol specific stuff here, using sType
    default:
        //this does not conform to MyProtocol
    ...
    }
}
11
Campbell_Souped

Vous devez déclarer le protocole en tant que @objc:

@objc protocol MyProtocol {
    ...
} 

Extrait du livre "Le Swift Langage de programmation") "d'Apple:

Vous pouvez vérifier la conformité du protocole uniquement si votre protocole est marqué avec l'attribut @objc, comme indiqué pour le protocole HasArea ci-dessus. Cet attribut indique que le protocole doit être exposé au code Objective-C et est décrit dans Utilisation de Swift avec Cocoa et Objective-C. Même si vous n'interagissez pas avec Objective-C, vous devez marquez vos protocoles avec l'attribut @objc si vous voulez pouvoir vérifier la conformité du protocole.

Notez également que les protocoles @objc ne peuvent être adoptés que par des classes et non par des structures ou des énumérations. Si vous marquez votre protocole avec @objc afin de vérifier la conformité, vous pourrez appliquer ce protocole uniquement aux types de classe.

5
rabbitinspace

let conforms = T.self est MyProtocol.Type

3
Carlos Chaguendo