2015-01-24 15 views
45

ho un protocollo che ho definito in questo modo:Swift: verifica se il tipo generico conforme al protocollo

protocol MyProtocol { 
    ... 
} 

devo anche una struttura generica:

struct MyStruct <T> { 
    ... 
} 

Infine ho una funzione generica:

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

Mi piacerebbe testare all'interno della funzione se il tipo T è conforme a MyProtocol. In sostanza mi piacerebbe essere in grado di fare (~ pseudocodice):

let conforms = T.self is MyProtocol 

Ma questo getta un errore di compilazione:

error: cannot downcast from 'T.Type' to [email protected] protocol type 'MyProtocol' 
    let conforms = T.self is MyProtocol 
        ~~~~~~^~~~~~~~~~~ 

Ho anche provato varianti, come T.self is MyProtocol.self, T is MyProtocol, e l'utilizzo di == invece di is. Finora non ho ottenuto da nessuna parte. Qualche idea?

risposta

4

bisogno dichiarate protocollo come @objc:

@objc protocol MyProtocol { 
    ... 
} 

da Apple "The Swift Programming Language" libro:

You can check for protocol conformance only if your protocol is marked with the @objc attribute, as seen for the HasArea protocol above. This attribute indicates that the protocol should be exposed to Objective-C code and is described in Using Swift with Cocoa and Objective-C. Even if you are not interoperating with Objective-C, you need to mark your protocols with the @objc attribute if you want to be able to check for protocol conformance.

Note also that @objc protocols can be adopted only by classes, and not by structures or enumerations. If you mark your protocol as @objc in order to check for conformance, you will be able to apply that protocol only to class types.

+0

Anche con questo, ho ancora ottenere lo stesso errore. '@objc protocollo MyProtocol {} struct MyStruct {} func myFunc (s: MyStruct ) -> T? { let conforms = T.self is MyProtocol } ' – Alex

+1

@Alex, è necessario creare un'istanza di tipo T prima di poter verificare la conformità del protocollo (come so) Se è necessario, digitare T deve essere solo di tipo conforme a MyProtocol, è possibile specificarlo: 'func myFunc (...) -> T?' – rabbitinspace

26

La risposta più semplice è: non lo fanno. Usa invece l'overloading e i vincoli e determina tutto in anticipo in fase di compilazione piuttosto che testare dinamicamente le cose in fase di runtime. Il controllo del tipo di runtime ei generici in fase di compilazione sono come la bistecca e il gelato: entrambi sono buoni, ma mescolarli è un po 'strano.

considerare qualcosa di simile:

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'essere lato negativo, non è possibile stabilire in modo dinamico se T supporta un protocollo in fase di esecuzione:

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

Ma, in tutta onestà, non si ha realmente è necessario farlo all'interno della tua funzione generica? Se non sei sicuro di quale sia il tipo attuale, l'opzione migliore potrebbe essere quella di capirlo in anticipo anziché rimandarlo a più tardi e spingerlo all'interno di una funzione generica per scoprirlo.

+11

+1, buona risposta. I controlli generici di tipo _Runtime e generici in fase di compilazione sono simili a bistecche e gelati - entrambi sono buoni, ma mescolarli è un po 'strano._ " – Stuart

+5

Sì ... eccetto per i casi limite in cui sovraccarico e vincoli non stanno andando a Fai il lavoro. Considera un metodo di estensione che implementa un protocollo, 'JSONEncodable', che richiede' init (json: JSON) throws'. Vogliamo 'Array' per implementare' JSONEncodable', ma solo se i suoi elementi sono anche 'JSONEncodable'. Non possiamo combinare una clausola di ereditarietà con i vincoli, quindi dobbiamo usare un tipo di controllo di tipo all'interno della nostra implementazione di 'init', e forse lanciare un errore se l'elemento' non è 'JSONEncodable'. Purtroppo, questo non sembra possibile AFAICT. –

+0

Dovrei aggiungere che l'enigma di cui sopra può essere risolto utilizzando un tipo intermedio come thunk, ma questa è una soluzione piuttosto inelegante. –

42

Un po 'in ritardo, ma è possibile verificare se qualcosa risponde al protocollo con as ? prova:

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

EDIT: un po' più corta:

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

E l'utilizzo di una guardia

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

puoi anche sfruttare lo switch case pattern matching di swift, se vuoi gestire più casi di ty Pe 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 
    ... 
    } 
} 
22

devo dire @alex vuole verificare se T tipo soddisfa protocollo piuttosto che s.E alcuni rispondenti non hanno visto chiaro.

check T tipo soddisfa protocollo come questo:

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

o

if T.self is MyProtocol.Type { 
    // T conform MyProtocol 
} 
+0

Questa è la risposta corretta. Grazie! – RyanM

Problemi correlati