Classe conforme al protocollo come parametro di funzione in Swift


91

In Objective-C, è possibile specificare una classe conforme a un protocollo come parametro del metodo. Ad esempio, potrei avere un metodo che consente solo un UIViewControllerche è conforme a UITableViewDataSource:

- (void)foo:(UIViewController<UITableViewDataSource> *)vc;

Non riesco a trovare un modo per farlo in Swift (forse non è ancora possibile). È possibile specificare più protocolli utilizzando func foo(obj: protocol<P1, P2>), ma come si richiede che anche l'oggetto appartenga a una classe particolare?


Potresti creare una classe personalizzata, ad esempio MyViewControllerClass, e assicurarti che la classe sia conforme al protocollo che ti interessa. Quindi dichiarare che l'argomento accetta quella classe personalizzata. Mi rendo conto che non funzionerebbe per ogni situazione, ma è un modo ... non una risposta alla tua domanda però. Più di una soluzione alternativa.
CommaToast

Risposte:


132

È possibile definire foouna funzione generica e utilizzare i vincoli di tipo per richiedere sia una classe che un protocollo.

Swift 4

func foo<T: UIViewController & UITableViewDataSource>(vc: T) {
    .....
}

Swift 3 (funziona anche per Swift 4)

func foo<T: UIViewController>(vc:T) where T:UITableViewDataSource { 
    ....
}

Swift 2

func foo<T: UIViewController where T: UITableViewDataSource>(vc: T) {
    // access UIViewController property
    let view = vc.view
    // call UITableViewDataSource method
    let sections = vc.numberOfSectionsInTableView?(tableView)
}

3
Penso che sia un po 'un peccato che ciò sia richiesto. Si spera che in futuro ci sarà una sintassi più pulita per questo, come protocol<>fornisce (ma protocol<>non può contenere tipi non di protocollo).
jtbandes

Questo mi rende tremendamente triste.
DCMaxxx

Solo per curiosità, non puoi scartare esplicitamente numberOfSectionsInTableViewperché è una funzione richiesta in UITableViewDataSource?
rb612

numberOfSectionsInTableView:è facoltativo, potresti pensare tableView:numberOfRowsInSection:.
Nate Cook

11
In Swift 3 questo sembra essere deprecato a partire da Xcode 8 beta 6 con una preferenza per:func foo<T: UIViewController>(vc:T) where T:UITableViewDataSource { ... }
LOP_Luke

29

In Swift 4 puoi ottenere questo risultato con il nuovo segno &:

let vc: UIViewController & UITableViewDataSource

17

La documentazione del libro Swift suggerisce di utilizzare vincoli di tipo con una clausola where:

func someFunction<C1: SomeClass where C1:SomeProtocol>(inParam: C1) {}

Ciò garantisce che "inParam" sia di tipo "SomeClass" a condizione che aderisca anche a "SomeProtocol". Hai anche il potere di specificare più clausole where delimitate da una virgola:

func itemsMatch<C1: SomeProtocol, C2: SomeProtocol where C1.ItemType == C2.ItemType,    C1.ItemType: SomeOtherProtocol>(foo: C1, bar: C2) -> Bool { return true }

1
Sarebbe stato bello vedere il link alla documentazione.
Raj

4

Con Swift 3, puoi fare quanto segue:

func foo(_ dataSource: UITableViewDataSource) {
    self.tableView.dataSource = dataSource
}

func foo(_ delegateAndDataSource: UITableViewDelegate & UITableViewDataSource) { 
    //Whatever
}

1
Questo si applica solo ai protocolli, non al protocollo e alla classe in swift 3.
Artem Goryaev

2

E in questo modo ?:

protocol MyProtocol {
    func getTableViewDataSource() -> UITableViewDataSource
    func getViewController() -> UIViewController
}

class MyVC : UIViewController, UITableViewDataSource, MyProtocol {

    // ...

    func getTableViewDataSource() -> UITableViewDataSource {
        return self
    }

    func getViewController() -> UIViewController {
        return self
    }
}

func foo(_ vc:MyProtocol) {
    vc.getTableViewDataSource() // working with UITableViewDataSource stuff
    vc.getViewController() // working with UIViewController stuff
}

2

Swift 5:

func foo(vc: UIViewController & UITableViewDataSource) {
    ...
}

Quindi essenzialmente la risposta di Jeroen sopra.


0

Nota nel settembre 2015 : questa era un'osservazione nei primi giorni di Swift.

Sembra impossibile. Apple ha questo fastidio anche in alcune delle sue API. Ecco un esempio di una classe appena introdotta in iOS 8 (a partire dalla beta 5):

UIInputViewController's textDocumentProxydi proprietà:

Definito in Objective-C come segue:

@property(nonatomic, readonly) NSObject<UITextDocumentProxy> *textDocumentProxy;

e in Swift:

var textDocumentProxy: NSObject! { get }

Link alla documentazione di Apple: https://developer.apple.com/library/prerelease/iOS/documentation/UIKit/Reference/UIInputViewController_Class/index.html#//apple_ref/occ/instp/UIInputViewController/textDocumentProxy


1
Sembra generato automaticamente: i protocolli Swift possono essere passati come oggetti. Teoricamente potrebbero semplicemente digitarevar textDocumentProxy: UITextDocumentProxy! { get }
atlex2

@ atlex2 Hai perso il tipo di classe NSObject a favore del tipo di protocollo UITextDocumentProxy.
titaniumdecoy

@titaniumdecoy No ti sbagli; hai ancora NSObject se UITextDocumentProxy è dichiarato come la maggior parte dei protocolli sono:@protocol MyAwesomeCallbacks <NSObject>
CommaToast

@CommaToast Not in Swift, di cui tratta questa domanda.
titaniumdecoy

@titaniumdecoy Sì, inizialmente avevi ragione. Ero confuso! Mi dispiace dire che ti sei sbagliato. Al rialzo hai ancora NSObjectProtocol ... in questo caso ... ma so che non è la stessa cosa.
CommaToast
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.