Classe conforme au protocole en tant que paramètre de fonction dans Swift

91

En Objective-C, il est possible de spécifier une classe conforme à un protocole en tant que paramètre de méthode. Par exemple, je pourrais avoir une méthode qui n'autorise qu'un UIViewControllerqui est conforme à UITableViewDataSource:

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

Je ne trouve pas de moyen de faire cela dans Swift (ce n'est peut-être pas encore possible). Vous pouvez spécifier plusieurs protocoles en utilisant func foo(obj: protocol<P1, P2>), mais comment exigez-vous que l'objet appartienne également à une classe particulière?

Martin Gordon
la source
Vous pouvez créer une classe personnalisée, par exemple MyViewControllerClass, et vous assurer que cette classe est conforme au protocole qui vous intéresse. Ensuite, déclarez que l'argument accepte cette classe personnalisée. Je me rends compte que cela ne fonctionnerait pas pour toutes les situations, mais c'est un moyen ... pas une réponse à votre question cependant. Plus d'une solution de contournement.
CommaToast

Réponses:

132

Vous pouvez définir foocomme fonction générique et utiliser des contraintes de type pour exiger à la fois une classe et un protocole.

Swift 4

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

Swift 3 (fonctionne également pour 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)
}
Nate Cook
la source
3
Je pense que c'est un peu malheureux que cela soit nécessaire. Espérons qu'à l'avenir, il y aura une syntaxe plus propre pour cela, comme protocol<>fournit (mais protocol<>ne peut pas contenir de types sans protocole).
jtbandes
Cela me rend tellement triste.
DCMaxxx
Juste par curiosité, ne pouvez-vous pas déballer explicitement numberOfSectionsInTableViewparce que c'est une fonction obligatoire UITableViewDataSource?
rb612
numberOfSectionsInTableView:est facultatif - vous pensez peut-être tableView:numberOfRowsInSection:.
Nate Cook
11
Dans Swift 3, cela semble être obsolète à partir de Xcode 8 beta 6 avec une préférence pour:func foo<T: UIViewController>(vc:T) where T:UITableViewDataSource { ... }
LOP_Luke
29

Dans Swift 4, vous pouvez y parvenir avec le nouveau signe &:

let vc: UIViewController & UITableViewDataSource
Jeroen Bakker
la source
17

La documentation du livre Swift suggère d'utiliser des contraintes de type avec une clause where:

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

Cela garantit que «inParam» est de type «SomeClass» à condition qu'il adhère également à «SomeProtocol». Vous avez même le pouvoir de spécifier plusieurs clauses where délimitées par une virgule:

func itemsMatch<C1: SomeProtocol, C2: SomeProtocol where C1.ItemType == C2.ItemType,    C1.ItemType: SomeOtherProtocol>(foo: C1, bar: C2) -> Bool { return true }
Jon Tsiros
la source
1
Un lien vers la documentation aurait été agréable à voir.
Raj
4

Avec Swift 3, vous pouvez effectuer les opérations suivantes:

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

func foo(_ delegateAndDataSource: UITableViewDelegate & UITableViewDataSource) { 
    //Whatever
}
Kalzem
la source
1
Cela ne s'applique qu'aux protocoles, pas au protocole et à la classe dans swift 3.
Artem Goryaev
2

Qu'en est-il de cette façon?:

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
}
MuHAOS
la source
2

Swift 5:

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

Donc essentiellement la réponse de Jeroen ci-dessus.

willtherussien
la source
0

Remarque en septembre 2015 : il s'agissait d'une observation aux débuts de Swift.

Cela semble impossible. Apple a également cette gêne dans certaines de ses API. Voici un exemple d'une classe nouvellement introduite dans iOS 8 (à partir de la version bêta 5):

UIInputViewControllerde » textDocumentProxybiens:

Défini dans Objective-C comme suit:

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

et dans Swift:

var textDocumentProxy: NSObject! { get }

Lien vers la documentation Apple: https://developer.apple.com/library/prerelease/iOS/documentation/UIKit/Reference/UIInputViewController_Class/index.html#//apple_ref/occ/instp/UIInputViewController/textDocumentProxy

Klaas
la source
1
Cela semble auto-généré: les protocoles Swift peuvent être transmis sous forme d'objets. Théoriquement, ils pourraient simplement tapervar textDocumentProxy: UITextDocumentProxy! { get }
atlex2
@ atlex2 Vous avez perdu le type de classe NSObject au profit du type de protocole UITextDocumentProxy.
titaniumdecoy
@titaniumdecoy Non, vous vous trompez; vous avez toujours NSObject si UITextDocumentProxy est déclaré comme la plupart des protocoles sont:@protocol MyAwesomeCallbacks <NSObject>
CommaToast
@CommaToast Pas dans Swift, c'est le sujet de cette question.
titaniumdecoy
@titaniumdecoy Ouais, tu avais raison à l'origine. J'étais confus! Désolé de dire que vous vous êtes trompé. Du côté positif, vous avez toujours NSObjectProtocol ... dans ce cas ... mais je sais que ce n'est pas la même chose.
CommaToast