In Swift, come posso dichiarare una variabile di un tipo specifico conforme a uno o più protocolli?


96

In Swift posso impostare esplicitamente il tipo di una variabile dichiarandolo come segue:

var object: TYPE_NAME

Se vogliamo fare un ulteriore passo avanti e dichiarare una variabile conforme a più protocolli, possiamo usare il protocol dichiarativo:

var object: protocol<ProtocolOne,ProtocolTwo>//etc

E se volessi dichiarare un oggetto conforme a uno o più protocolli ed è anche di un tipo di classe base specifico? L'equivalente Objective-C sarebbe simile a questo:

NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;

In Swift mi aspetto che assomigli a questo:

var object: TYPE_NAME,ProtocolOne//etc

Questo ci offre la flessibilità di poter gestire l'implementazione del tipo di base e l'interfaccia aggiunta definita nel protocollo.

C'è un altro modo più ovvio in cui potrei mancare?

Esempio

Ad esempio, diciamo che ho una UITableViewCellfabbrica che è responsabile della restituzione delle cellule conformi a un protocollo. Possiamo facilmente impostare una funzione generica che restituisce celle conformi a un protocollo:

class CellFactory {
    class func createCellForItem<T: UITableViewCell where T:MyProtocol >(item: SpecialItem,tableView: UITableView) -> T {
        //etc
    }
}

in seguito desidero rimuovere dalla coda queste celle sfruttando sia il tipo che il protocollo

var cell: MyProtocol = CellFactory.createCellForItem(somethingAtIndexPath) as UITableViewCell

Ciò restituisce un errore perché una cella della vista tabella non è conforme al protocollo ...

Vorrei essere in grado di specificare che la cella è una UITableViewCelle è conforme alla MyProtocolnella dichiarazione della variabile?

Giustificazione

Se si ha familiarità con Factory Pattern, ciò avrebbe senso nel contesto della possibilità di restituire oggetti di una particolare classe che implementano una determinata interfaccia.

Proprio come nel mio esempio, a volte ci piace definire interfacce che abbiano senso quando applicate a un particolare oggetto. Il mio esempio della cella della vista tabella è una di queste giustificazioni.

Sebbene il tipo fornito non sia esattamente conforme all'interfaccia menzionata, l'oggetto restituito dalla fabbrica lo fa e quindi vorrei la flessibilità nell'interagire sia con il tipo di classe base che con l'interfaccia del protocollo dichiarato


Mi dispiace, ma qual è il punto in Swift. I tipi sanno già quali protocolli si conformano. Cosa non usa solo il tipo?
Kirsteins

1
@Kirsteins No, a meno che il tipo non venga restituito da una fabbrica e sia quindi un tipo generico con una classe base comune
Daniel Galasko

Si prega di fornire un esempio, se possibile.
Kirsteins

NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;. Questo oggetto sembra abbastanza inutile in quanto NSSomethingsa già a cosa è conforme. Se non è conforme a uno dei protocolli, <>si verificheranno dei unrecognised selector ...crash. Ciò non fornisce alcuna sicurezza di tipo.
Kirsteins

@Kirsteins Per favore guarda di nuovo il mio esempio, è usato quando sai che l'oggetto che la tua fabbrica vende è di una particolare classe base conforme a un protocollo specificato
Daniel Galasko

Risposte:


72

In Swift 4 è ora possibile dichiarare una variabile che è una sottoclasse di un tipo e implementa uno o più protocolli contemporaneamente.

var myVariable: MyClass & MyProtocol & MySecondProtocol

Per eseguire una variabile facoltativa:

var myVariable: (MyClass & MyProtocol & MySecondProtocol)?

o come parametro di un metodo:

func shakeEm(controls: [UIControl & Shakeable]) {}

Apple lo ha annunciato al WWDC 2017 nella Sessione 402: Novità in Swift

Secondo, voglio parlare della composizione di classi e protocolli. Quindi, qui ho introdotto questo protocollo shakable per un elemento dell'interfaccia utente che può dare un piccolo effetto di scuotimento per attirare l'attenzione su se stesso. E sono andato avanti e ho esteso alcune delle classi UIKit per fornire effettivamente questa funzionalità di shake. E ora voglio scrivere qualcosa che sembra semplice. Voglio solo scrivere una funzione che prenda un mucchio di controlli che sono modificabili e scuota quelli che sono abilitati per attirare l'attenzione su di loro. Che tipo posso scrivere qui in questo array? In realtà è frustrante e complicato. Quindi, potrei provare a utilizzare un controllo dell'interfaccia utente. Ma non tutti i controlli dell'interfaccia utente sono modificabili in questo gioco. Potrei provare shakable, ma non tutti gli shakable sono controlli dell'interfaccia utente. E in realtà non c'è un buon modo per rappresentarlo in Swift 3.Swift 4 introduce il concetto di comporre una classe con un numero qualsiasi di protocolli.


3
Basta aggiungere un collegamento alla proposta di evoluzione rapida github.com/apple/swift-evolution/blob/master/proposals/…
Daniel Galasko

Grazie Philipp!
Omar Albeik

e se fosse necessaria una variabile opzionale di questo tipo?
Vyachaslav Gerchicov

2
@VyachaslavGerchicov: puoi racchiudere tra parentesi e poi il punto interrogativo in questo modo: var myVariable: (MyClass & MyProtocol & MySecondProtocol)?
Philipp Otto

30

Non puoi dichiarare variabili simili

var object:Base,protocol<ProtocolOne,ProtocolTwo> = ...

né dichiarare il tipo di ritorno della funzione come

func someFunc() -> Base,protocol<MyProtocol,Protocol2> { ... }

Puoi dichiarare come un parametro di funzione come questo, ma è fondamentalmente up-casting.

func someFunc<T:Base where T:protocol<MyProtocol1,MyProtocol2>>(val:T) {
    // here, `val` is guaranteed to be `Base` and conforms `MyProtocol` and `MyProtocol2`
}

class SubClass:BaseClass, MyProtocol1, MyProtocol2 {
   //...
}

let val = SubClass()
someFunc(val)

A partire da ora, tutto ciò che puoi fare è come:

class CellFactory {
    class func createCellForItem(item: SpecialItem) -> UITableViewCell {
        return ... // any UITableViewCell subclass
    }
}

let cell = CellFactory.createCellForItem(special)
if let asProtocol = cell as? protocol<MyProtocol1,MyProtocol2> {
    asProtocol.protocolMethod()
    cell.cellMethod()
}

Con questo, tecnicamente cellè identico a asProtocol.

Ma, come per il compilatore, cellha UITableViewCellsolo l' interfaccia , mentre asProtocolha solo l'interfaccia dei protocolli. Quindi, quando vuoi chiamareUITableViewCell i metodi di, devi usare cellvariabile. Quando vuoi chiamare il metodo dei protocolli, usa la asProtocolvariabile.

Se sei sicuro che la cella sia conforme ai protocolli non devi usare if let ... as? ... {}. piace:

let cell = CellFactory.createCellForItem(special)
let asProtocol = cell as protocol<MyProtocol1,MyProtocol2>

Poiché la fabbrica specifica i tipi di restituzione, tecnicamente non ho bisogno di eseguire il cast opzionale? Potrei semplicemente fare affidamento sulla digitazione implicita di swift per eseguire la digitazione in cui dichiaro esplicitamente i protocolli?
Daniel Galasko

Non capisco cosa intendi, scusa per le mie cattive abilità in inglese. Se stai dicendo di -> UITableViewCell<MyProtocol>, questo non è valido, perché UITableViewCellnon è un tipo generico. Penso che questo non si compili nemmeno.
rintaro

Non mi riferisco alla tua implementazione generica, ma piuttosto alla tua illustrazione di esempio dell'implementazione. dove dici lascia asProtocol = ...
Daniel Galasko

oppure, potrei semplicemente fare: var cell: protocol <ProtocolOne, ProtocolTwo> = someObject come UITableViewCell e ottenere il vantaggio di entrambi in una variabile
Daniel Galasko

2
Non credo proprio. Anche se si potesse fare così, cellha solo metodi di protocolli (per il compilatore).
rintaro

2

Sfortunatamente, Swift non supporta la conformità del protocollo a livello di oggetto. Tuttavia, c'è una soluzione un po 'imbarazzante che può servire ai tuoi scopi.

struct VCWithSomeProtocol {
    let protocol: SomeProtocol
    let viewController: UIViewController

    init<T: UIViewController>(vc: T) where T: SomeProtocol {
        self.protocol = vc
        self.viewController = vc
    }
}

Quindi, ovunque tu abbia bisogno di fare qualcosa che ha UIViewController, dovresti accedere all'aspetto .viewController della struttura e qualsiasi cosa ti serva all'aspetto del protocollo, faresti riferimento al .protocol.

Per esempio:

class SomeClass {
   let mySpecialViewController: VCWithSomeProtocol

   init<T: UIViewController>(injectedViewController: T) where T: SomeProtocol {
       self.mySpecialViewController = VCWithSomeProtocol(vc: injectedViewController)
   }
}

Ora ogni volta che hai bisogno di mySpecialViewController per fare qualsiasi cosa relativa a UIViewController, fai riferimento a mySpecialViewController.viewController e ogni volta che ne hai bisogno per eseguire alcune funzioni di protocollo, fai riferimento a mySpecialViewController.protocol.

Si spera che Swift 4 ci consentirà di dichiarare un oggetto a cui sono associati dei protocolli in futuro. Ma per ora funziona.

Spero che questo ti aiuti!


1

EDIT: Mi sbagliavo , ma se qualcun altro ha letto questo malinteso come me, lascio questa risposta là fuori. L'OP ha chiesto di verificare la conformità al protocollo dell'oggetto di una data sottoclasse, e questa è un'altra storia, come mostra la risposta accettata. Questa risposta parla della conformità del protocollo per la classe base.

Forse mi sbaglio, ma non stai parlando di aggiungere la conformità del protocollo alla UITableCellViewclasse? In tal caso il protocollo è esteso alla classe base e non all'oggetto. Consulta la documentazione di Apple sulla dichiarazione di adozione del protocollo con un'estensione che nel tuo caso sarebbe qualcosa del tipo:

extension UITableCellView : ProtocolOne {}

// Or alternatively if you need to add a method, protocolMethod()
extension UITableCellView : ProcotolTwo {
   func protocolTwoMethod() -> String {
     return "Compliant method"
   }
}

Oltre alla documentazione Swift già citata, vedi anche l'articolo di Nate Cooks Funzioni generiche per tipi incompatibili con ulteriori esempi.

Questo ci offre la flessibilità di poter gestire l'implementazione del tipo di base e l'interfaccia aggiunta definita nel protocollo.

C'è un altro modo più ovvio in cui potrei mancare?

L'adozione del protocollo farà proprio questo, farà aderire un oggetto al protocollo dato. Siate comunque consapevoli del lato negativo, che una variabile di un dato tipo di protocollo non conosce nulla al di fuori del protocollo. Ma questo può essere aggirato definendo un protocollo che abbia tutti i metodi / variabili / ...

Sebbene il tipo fornito non sia esattamente conforme all'interfaccia menzionata, l'oggetto restituito dalla fabbrica lo fa e quindi vorrei la flessibilità nell'interagire sia con il tipo di classe base che con l'interfaccia del protocollo dichiarato

Se desideri che un metodo generico, variabile sia conforme sia a un protocollo che ai tipi di classe base, potresti essere sfortunato. Ma sembra che sia necessario definire il protocollo abbastanza ampio da avere i metodi di conformità necessari, e allo stesso tempo abbastanza stretto da avere la possibilità di adottarlo alle classi base senza troppo lavoro (cioè semplicemente dichiarando che una classe è conforme al protocollo).


1
Non è di questo che sto parlando, ma grazie :) Volevo essere in grado di interfacciarmi con un oggetto sia attraverso la sua classe che tramite un protocollo specifico. Proprio come in obj-c posso fare NSObject <MyProtocol> obj = ... Inutile dire che questo non può essere fatto in modo rapido, devi lanciare l'oggetto al suo protocollo
Daniel Galasko

0

Una volta ho avuto una situazione simile durante il tentativo di collegare le mie connessioni interactor generiche in Storyboard (IB non ti consentirà di connettere prese a protocolli, solo istanze di oggetti), che ho aggirato semplicemente mascherando l'ivar pubblico della classe base con un computer privato calcolato proprietà. Sebbene ciò non impedisca a qualcuno di eseguire assegnazioni illegali di per sé, fornisce un modo conveniente per prevenire in modo sicuro qualsiasi interazione indesiderata con un'istanza non conforme in fase di esecuzione. (ovvero impedire di chiamare metodi delegati a oggetti non conformi al protocollo.)

Esempio:

@objc protocol SomeInteractorInputProtocol {
    func getSomeString()
}

@objc protocol SomeInteractorOutputProtocol {
    optional func receiveSomeString(value:String)
}

@objc class SomeInteractor: NSObject, SomeInteractorInputProtocol {

    @IBOutlet var outputReceiver : AnyObject? = nil

    private var protocolOutputReceiver : SomeInteractorOutputProtocol? {
        get { return self.outputReceiver as? SomeInteractorOutputProtocol }
    }

    func getSomeString() {
        let aString = "This is some string."
        self.protocolOutputReceiver?.receiveSomeString?(aString)
    }
}

Il "outputReceiver" è dichiarato opzionale, così come il privato "protocolOutputReceiver". Accedendo sempre all'outputReceiver (noto anche come delegato) tramite quest'ultimo (la proprietà calcolata), filtro efficacemente gli oggetti che non sono conformi al protocollo. Ora posso semplicemente usare il concatenamento opzionale per chiamare in modo sicuro l'oggetto delegato indipendentemente dal fatto che implementi il ​​protocollo o addirittura esista.

Per applicare questo alla tua situazione, puoi fare in modo che l'ivar pubblico sia di tipo "YourBaseClass?" (al contrario di AnyObject) e utilizzare la proprietà calcolata privata per applicare la conformità al protocollo. FWIW.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.