Protokollkonforme Klasse als Funktionsparameter in Swift


91

In Objective-C ist es möglich, eine Klasse, die einem Protokoll entspricht, als Methodenparameter anzugeben. Zum Beispiel könnte ich eine Methode haben, die nur eine erlaubt UIViewController, die konform ist mit UITableViewDataSource:

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

Ich kann in Swift keinen Weg finden, dies zu tun (vielleicht ist es noch nicht möglich). Sie können mehrere Protokolle mit angeben func foo(obj: protocol<P1, P2>), aber wie benötigen Sie, dass das Objekt auch einer bestimmten Klasse angehört?


Sie können eine benutzerdefinierte Klasse erstellen, z. B. MyViewControllerClass, und sicherstellen, dass die Klasse dem von Ihnen gewünschten Protokoll entspricht. Deklarieren Sie dann, dass das Argument diese benutzerdefinierte Klasse akzeptiert. Mir ist klar, dass es nicht in jeder Situation funktionieren würde, aber es ist ein Weg ... keine Antwort auf Ihre Frage. Eher eine Problemumgehung.
CommaToast

Antworten:


132

Sie können fooeine generische Funktion definieren und Typeinschränkungen verwenden, um sowohl eine Klasse als auch ein Protokoll zu benötigen.

Swift 4

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

Swift 3 (funktioniert auch für 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
Ich finde es etwas bedauerlich, dass dies erforderlich ist. Hoffentlich gibt es in Zukunft eine sauberere Syntax dafür, wie sie protocol<>bereitgestellt wird ( protocol<>kann aber keine Nicht-Protokolltypen enthalten).
Jtbandes

Das macht mich sooooo traurig.
DCMaxxx

Können numberOfSectionsInTableViewSie aus Neugier nicht explizit auspacken, weil dies eine erforderliche Funktion ist UITableViewDataSource?
rb612

numberOfSectionsInTableView:ist optional - Sie könnten daran denken tableView:numberOfRowsInSection:.
Nate Cook

11
In Swift 3 scheint dies ab Xcode 8 Beta 6 veraltet zu sein, mit einer Präferenz für:func foo<T: UIViewController>(vc:T) where T:UITableViewDataSource { ... }
LOP_Luke

29

In Swift 4 können Sie dies mit dem neuen & -Zeichen erreichen:

let vc: UIViewController & UITableViewDataSource

17

In der Swift-Buchdokumentation wird vorgeschlagen, Typeinschränkungen mit einer where-Klausel zu verwenden:

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

Dies garantiert, dass "inParam" vom Typ "SomeClass" ist, mit der Bedingung, dass es auch "SomeProtocol" einhält. Sie können sogar mehrere where-Klauseln angeben, die durch ein Komma getrennt sind:

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

1
Der Link zur Dokumentation wäre schön zu sehen gewesen.
Raj

4

Mit Swift 3 können Sie Folgendes tun:

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

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

1
Dies gilt nur für Protokolle, nicht für Protokoll und Klasse in Swift 3.
Artem Goryaev

2

Was ist mit diesem Weg?:

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
}


0

Anmerkung im September 2015 : Dies war eine Beobachtung in den frühen Tagen von Swift.

Es scheint unmöglich zu sein. Apple hat diesen Ärger auch in einigen seiner APIs. Hier ist ein Beispiel aus einer neu eingeführten Klasse in iOS 8 (ab Beta 5):

UIInputViewController's textDocumentProxyEigentum:

In Ziel-C wie folgt definiert:

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

und in Swift:

var textDocumentProxy: NSObject! { get }

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


1
Dies scheint automatisch generiert zu werden: Schnelle Protokolle können als Objekte weitergegeben werden. Theoretisch könnten sie einfach tippenvar textDocumentProxy: UITextDocumentProxy! { get }
atlex2

@ atlex2 Sie haben den Klassentyp NSObject zugunsten des Protokolltyps UITextDocumentProxy verloren.
Titandecoy

@titaniumdecoy Nein, du liegst falsch; Sie haben immer noch NSObject, wenn UITextDocumentProxy wie die meisten Protokolle deklariert ist:@protocol MyAwesomeCallbacks <NSObject>
CommaToast

@CommaToast Nicht in Swift, darum geht es in dieser Frage.
titaniumdecoy

@titaniumdecoy Ja, du hattest ursprünglich recht. Ich war verwirrt! Tut mir leid zu sagen, dass Sie sich geirrt haben. Auf der anderen Seite haben Sie immer noch NSObjectProtocol ... in diesem Fall ... aber ich weiß, dass es nicht dasselbe ist.
CommaToast
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.