Swift: Überprüfen Sie, ob der generische Typ dem Protokoll entspricht

78

Ich habe ein Protokoll, das ich so definiert habe:

protocol MyProtocol {
   ...
}

Ich habe auch eine generische Struktur:

struct MyStruct <T>  {
    ...
}

Endlich habe ich eine generische Funktion:

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

Ich möchte innerhalb der Funktion testen, ob der Typ T MyProtocol entspricht. Im Wesentlichen möchte ich in der Lage sein (~ Pseudocode):

let conforms = T.self is MyProtocol

Dies führt jedoch zu einem Compilerfehler:

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

Ich habe auch Variationen ausprobiert, wie T.self is MyProtocol.self, T is MyProtocolund die Verwendung ==statt is. Bisher bin ich nirgendwo hingekommen. Irgendwelche Ideen?

Alex
quelle

Antworten:

78

Ich muss sagen, @Alex möchte überprüfen, ob der TTyp dem Protokoll entspricht und nicht s. Und einige Antwortende sahen nicht klar.

Überprüfen Sie T, ob der Typ dem folgenden Protokoll entspricht:

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

oder

if T.self is MyProtocol.Type {
    //  T conform MyProtocol
}
Maquannene
quelle
10
Was ist, wenn MyProtocol CaseIterable ist (ein Protokoll mit zugeordnetem Typ)? Es wird ein Fehler angezeigt: 'CaseIterable' kann nur als generische Einschränkung verwendet werden, da Self- oder zugehörige Typanforderungen vorliegen.
Zgjie
73

Ein bisschen spät, aber Sie können testen, ob etwas mit dem as ?Test auf das Protokoll reagiert :

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

EDIT: etwas kürzer:

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

Und mit einer Wache:

guard let _ = self as? MyProtocol else {
    // doesn't match
    return
}
jlngdt
quelle
4
Dies erfordert eine Instanz von T, aber die Frage fragt nach generischem TYP. Somit ist Carlos Antwort besser: stackoverflow.com/a/52787263/1311272
Sajjon
Carlos Chaguendo Antwort sollte die akzeptierte Antwort IMO sein.
J.beenie
Dies beantwortet die Frage nicht
Jonathan.
35

Die einfachste Antwort lautet: Tu das nicht. Verwenden Sie stattdessen Überladung und Einschränkungen und bestimmen Sie alles zur Kompilierungszeit im Voraus, anstatt zur Laufzeit dynamisch zu testen. Runtime Type Checking und Generika zur Kompilierungszeit sind wie Steak und Eis - beide sind nett, aber das Mischen ist etwas seltsam.

Betrachten Sie so etwas:

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

Der Nachteil ist, dass Sie nicht dynamisch feststellen können, ob T zur Laufzeit ein Protokoll unterstützt:

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

Aber, ganz ehrlich, müssen Sie das wirklich in Ihrer generischen Funktion tun? Wenn Sie sich nicht sicher sind, um welchen Typ es sich tatsächlich handelt, ist es möglicherweise besser, dies im Voraus herauszufinden, als es auf einen späteren Zeitpunkt zu verschieben und es in einer generischen Funktion zu finden, um dies herauszufinden.

Fluggeschwindigkeit
quelle
17
+1, gute Antwort. Besonders gut gefallen " Runtime Type Checking und Generika zur Kompilierungszeit sind wie Steak und Eis - beide sind nett, aber das Mischen ist etwas seltsam. " Stuart
Stuart
6
Ja… mit Ausnahme der Randfälle, in denen Überlastung und Einschränkungen den Job einfach nicht erledigen. Stellen Sie sich eine Erweiterungsmethode vor, die ein Protokoll implementiert JSONEncodable, das erforderlich ist init(json: JSON) throws. Wir wollen Arrayimplementieren JSONEncodable, aber nur, wenn seine Elemente auch sind JSONEncodable. Wir können eine Vererbungsklausel nicht mit Einschränkungen kombinieren, daher müssen wir in unserer Implementierung von initeine Art Typprüfung verwenden und möglicherweise einen Fehler auslösen, wenn dies Elementnicht der Fall ist JSONEncodable. Leider scheint dies AFAICT nicht möglich zu sein.
Gregory Higley
Ich sollte hinzufügen, dass das obige Rätsel gelöst werden kann, indem ein Zwischentyp als Thunk verwendet wird, aber das ist eine ziemlich unelegante Lösung.
Gregory Higley
@ GregoryHigley, der jetzt mit bedingter Konformität in Swift 4.1 ( swift.org/blog/conditional-conformance ) möglich sein sollte
mj_jimenez
Es ist cool, wie man MyStructmit oder ohne Hinweis konstruieren kann <Type>und es kann sagen, was zu tun ist. Für andere, die den Code ausprobieren, benötigt Swift 4 ein _Argument für das erste Konstruktor
snakeoil
13

Sie können auch den Switch Case Pattern Matching von Swift nutzen , wenn Sie mehrere Fälle vom Typ behandeln möchten 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
    ...
    }
}
Campbell_Souped
quelle
Dies beantwortet auch nicht die Frage, da das Testen in einer Instanz eher dem Protokoll als dem Typ direkt entspricht.
Jonathan.
11

let konform = T.self ist MyProtocol.Type

Carlos Chaguendo
quelle
1
Dies ist die beste Antwort, da sie die ursprüngliche Frage "Wie geht das?" Beantwortet let conforms = T.self is MyProtocol.
Leslie Godwin
Einverstanden, beste Antwort auf die Frage!
Jordi Puigdellívol
5

Sie müssen das Protokoll wie folgt deklarieren @objc:

@objc protocol MyProtocol {
    ...
} 

Aus Apples Buch "The Swift Programming Language":

Sie können die Protokollkonformität nur überprüfen, wenn Ihr Protokoll mit dem Attribut @objc gekennzeichnet ist, wie im obigen HasArea-Protokoll dargestellt. Dieses Attribut gibt an, dass das Protokoll dem Objective-C-Code ausgesetzt werden soll, und wird unter Verwenden von Swift mit Cocoa und Objective-C beschrieben. Auch wenn Sie nicht mit Objective-C zusammenarbeiten, müssen Sie Ihre Protokolle mit dem Attribut @objc markieren, wenn Sie die Protokollkonformität überprüfen möchten.

Beachten Sie auch, dass @ objc-Protokolle nur von Klassen und nicht von Strukturen oder Aufzählungen übernommen werden können. Wenn Sie Ihr Protokoll als @objc markieren, um die Konformität zu überprüfen, können Sie dieses Protokoll nur auf Klassentypen anwenden.

Kaninchenraum
quelle
Trotzdem bekomme ich immer noch den gleichen Fehler. @objc protocol MyProtocol {} struct MyStruct <T> {} func myFunc <T> (s: MyStruct<T>) -> T? { let conforms = T.self is MyProtocol }
Alex
1
@Alex, Sie müssen eine Instanz vom Typ T erstellen, bevor Sie die Protokollkonformität überprüfen können (wie ich weiß). Wenn Sie den Typ T benötigen, der nur vom Typ sein muss, der MyProtocol entspricht, können Sie func myFunc<T: MyProtocol>(...) -> T?
Folgendes
2

Eine moderne Antwort lautet wie folgt: (Swift 5.1)

func myFunc < T: MyProtocol> (s: MyStruct<T>) -> T? {    ... }
Steven
quelle
4
?? Wie beantwortet das die Frage?
Peter Schorn
0

Für Testfälle überprüfe ich die Konformität wie folgt:

let conforms: Bool = (Controller.self as Any) is Protocol.Type
Daewizal Forshizal
quelle