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?

Martin Gordon
quelle
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)
}
Nate Cook
quelle
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
Jeroen Bakker
quelle
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 }
Jon Tsiros
quelle
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
}
Kalzem
quelle
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
}
MuHAOS
quelle
2

Swift 5:

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

Also im Wesentlichen Jeroens Antwort oben.

willtherussian
quelle
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

Klaas
quelle
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