Klasa zgodna z protokołem jako parametr funkcji w języku Swift

91

W Objective-C można określić klasę zgodną z protokołem jako parametr metody. Na przykład mógłbym mieć metodę, która zezwala tylko na to, UIViewControllerco jest zgodne z UITableViewDataSource:

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

Nie mogę znaleźć sposobu, aby to zrobić w Swift (być może nie jest to jeszcze możliwe). Możesz określić wiele protokołów przy użyciu func foo(obj: protocol<P1, P2>), ale jak chcesz, aby obiekt był również określonej klasy?

Martin Gordon
źródło
Możesz utworzyć klasę niestandardową, na przykład MyViewControllerClass, i upewnić się, że klasa jest zgodna z protokołem, na którym Ci zależy. Następnie zadeklaruj argument, który akceptuje tę klasę niestandardową. Zdaję sobie sprawę, że to nie zadziała w każdej sytuacji, ale to sposób ... nie jest to jednak odpowiedź na twoje pytanie. Więcej obejścia.
CommaToast

Odpowiedzi:

133

Możesz zdefiniować foojako funkcję ogólną i użyć ograniczeń typu, aby wymagać zarówno klasy, jak i protokołu.

Szybki 4

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

Swift 3 (działa również dla Swift 4)

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

Szybki 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
źródło
3
Myślę, że to trochę niefortunne, że jest to wymagane. Miejmy nadzieję, że w przyszłości będzie do tego czystsza składnia, na przykład protocol<>zapewnia (ale protocol<>nie może zawierać typów innych niż protokoły).
jtbandes
To sprawia, że ​​jestem taki smutny.
DCMaxxx,
Tak z ciekawości, czy nie możesz jawnie rozpakować, numberOfSectionsInTableViewponieważ jest to wymagana funkcja UITableViewDataSource?
rb612
numberOfSectionsInTableView:jest opcjonalne - możesz o tym pomyśleć tableView:numberOfRowsInSection:.
Nate Cook
11
W Swift 3 wydaje się to być przestarzałe od Xcode 8 beta 6 z preferencją dla:func foo<T: UIViewController>(vc:T) where T:UITableViewDataSource { ... }
LOP_Luke
29

W Swift 4 możesz to osiągnąć za pomocą nowego znaku &:

let vc: UIViewController & UITableViewDataSource
Jeroen Bakker
źródło
17

Dokumentacja książki Swift sugeruje, że używasz ograniczeń typu z klauzulą ​​where:

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

Gwarantuje to, że element „inParam” jest typu „SomeClass” z warunkiem, że jest również zgodny z „SomeProtocol”. Masz nawet możliwość określenia wielu, gdzie klauzule oddzielone przecinkami:

func itemsMatch<C1: SomeProtocol, C2: SomeProtocol where C1.ItemType == C2.ItemType,    C1.ItemType: SomeOtherProtocol>(foo: C1, bar: C2) -> Bool { return true }
Jony T.
źródło
1
Miło byłoby zobaczyć link do dokumentacji.
Raj
4

Dzięki Swift 3 możesz wykonać następujące czynności:

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

func foo(_ delegateAndDataSource: UITableViewDelegate & UITableViewDataSource) { 
    //Whatever
}
Kalzem
źródło
1
Dotyczy to tylko protokołów, a nie protokołów i klas w Swift 3.
Artem Goryaev
3

Swift 5:

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

Tak więc zasadniczo powyższa odpowiedź Jeroena .

willtherussian
źródło
2

A co w ten sposób ?:

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
źródło
0

Uwaga we wrześniu 2015 r . : To była obserwacja z wczesnych dni Swift.

Wydaje się to niemożliwe. Apple ma tę irytację również w niektórych swoich interfejsach API. Oto jeden przykład z nowo wprowadzonej klasy w iOS 8 (od wersji beta 5):

UIInputViewController„s textDocumentProxynieruchomości:

Zdefiniowany w celu C w następujący sposób:

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

iw Swift:

var textDocumentProxy: NSObject! { get }

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

Klaas
źródło
1
Wydaje się, że jest to generowane automatycznie: protokoły Swift mogą być przekazywane jako obiekty. Teoretycznie mogliby po prostu wpisaćvar textDocumentProxy: UITextDocumentProxy! { get }
atlex2
@ atlex2 Straciłeś typ klasy NSObject na rzecz typu protokołu UITextDocumentProxy.
titaniumdecoy
@titaniumdecoy Nie, mylisz się; nadal masz NSObject, jeśli zadeklarowano UITextDocumentProxy, tak jak większość protokołów:@protocol MyAwesomeCallbacks <NSObject>
CommaToast
@CommaToast Not in Swift, o co chodzi w tym pytaniu.
titaniumdecoy
@titaniumdecoy Tak, początkowo miałeś rację. Byłem zmieszany! Przepraszam, że się myliłeś. Z drugiej strony nadal masz NSObjectProtocol ... w tym przypadku ... ale wiem, że to nie to samo.
CommaToast