Kelas yang sesuai dengan protokol sebagai parameter fungsi di Swift

91

Di Objective-C, dimungkinkan untuk menentukan kelas yang sesuai dengan protokol sebagai parameter metode. Misalnya, saya dapat memiliki metode yang hanya mengizinkan a UIViewControlleryang sesuai dengan UITableViewDataSource:

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

Saya tidak dapat menemukan cara untuk melakukan ini di Swift (mungkin belum memungkinkan). Anda dapat menentukan beberapa protokol menggunakan func foo(obj: protocol<P1, P2>), tetapi bagaimana Anda meminta objek dari kelas tertentu juga?

Martin Gordon
sumber
Anda dapat membuat kelas khusus, misalnya MyViewControllerClass, dan memastikan bahwa kelas tersebut sesuai dengan protokol yang Anda pedulikan. Kemudian deklarasikan argumen menerima kelas kustom itu. Saya menyadari itu tidak akan berhasil untuk setiap situasi tetapi, ini adalah cara ... bukan jawaban atas pertanyaan Anda. Lebih dari sebuah solusi.
CommaToast

Jawaban:

133

Anda dapat mendefinisikan foosebagai fungsi generik dan menggunakan batasan tipe untuk membutuhkan kelas dan protokol.

Cepat 4

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

Swift 3 (juga berfungsi untuk Swift 4)

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

Cepat 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
sumber
3
Saya pikir agak disayangkan hal ini diperlukan. Semoga kedepannya akan ada sintaks yang lebih bersih untuk ini, seperti protocol<>sediakan (tapi protocol<>tidak bisa berisi tipe non-protokol).
jtbandes
Ini membuatku sangat sedih.
DCMaxxx
Hanya ingin tahu, tidak bisakah Anda membuka bungkusnya secara eksplisit numberOfSectionsInTableViewkarena ini adalah fungsi yang diwajibkan UITableViewDataSource?
rb612
numberOfSectionsInTableView:adalah opsional — Anda mungkin memikirkan tableView:numberOfRowsInSection:.
Nate Cook
11
Di Swift 3 ini tampaknya tidak digunakan lagi pada Xcode 8 beta 6 dengan preferensi untuk:func foo<T: UIViewController>(vc:T) where T:UITableViewDataSource { ... }
LOP_Luke
29

Di Swift 4 Anda dapat mencapai ini dengan & tanda baru:

let vc: UIViewController & UITableViewDataSource
Jeroen Bakker
sumber
17

Dokumentasi buku Swift menyarankan agar Anda menggunakan batasan tipe dengan klausa where:

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

Ini menjamin bahwa "inParam" adalah jenis "SomeClass" dengan syarat bahwa ia juga mematuhi "SomeProtocol". Anda bahkan memiliki kekuatan untuk menentukan beberapa klausa yang dipisahkan oleh koma:

func itemsMatch<C1: SomeProtocol, C2: SomeProtocol where C1.ItemType == C2.ItemType,    C1.ItemType: SomeOtherProtocol>(foo: C1, bar: C2) -> Bool { return true }
Jony T
sumber
1
Tautan ke dokumentasi pasti menyenangkan untuk dilihat.
Raj
4

Dengan Swift 3, Anda dapat melakukan hal berikut:

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

func foo(_ delegateAndDataSource: UITableViewDelegate & UITableViewDataSource) { 
    //Whatever
}
Kalzem
sumber
1
Ini hanya berlaku untuk protokol, bukan protokol & kelas di swift 3.
Artem Goryaev
3

Cepat 5:

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

Jadi intinya jawaban Jeroen di atas.

willtherussian
sumber
2

Bagaimana dengan cara ini ?:

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
sumber
0

Catatan pada September 2015 : Ini adalah pengamatan di hari-hari awal Swift.

Sepertinya tidak mungkin. Apple juga mengalami gangguan ini di beberapa API mereka. Berikut adalah salah satu contoh dari kelas yang baru diperkenalkan di iOS 8 (mulai beta 5):

UIInputViewController's textDocumentProxyproperti:

Didefinisikan dalam Objective-C sebagai berikut:

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

dan di Swift:

var textDocumentProxy: NSObject! { get }

Tautan ke dokumentasi Apple: https://developer.apple.com/library/prerelease/iOS/documentation/UIKit/Reference/UIInputViewController_Class/index.html#//apple_ref/occ/instp/UIInputViewController/textDocumentProxy

Klaas
sumber
1
Ini tampaknya dibuat secara otomatis: Protokol Swift dapat diedarkan sebagai objek. Secara teoritis mereka bisa mengetikvar textDocumentProxy: UITextDocumentProxy! { get }
atlex2
@ atlex2 Anda telah kehilangan jenis kelas NSObject yang mendukung jenis protokol UITextDocumentProxy.
titaniumdecoy
@titaniumdecoy Tidak, Anda salah; Anda masih memiliki NSObject jika UITextDocumentProxy dideklarasikan seperti kebanyakan protokol:@protocol MyAwesomeCallbacks <NSObject>
CommaToast
@CommaToast Tidak dalam Swift, yang menjadi pokok pertanyaan ini.
titaniumdecoy
@titaniumdecoy Ya, Anda pada awalnya benar. Saya bingung! Maaf untuk mengatakan Anda salah. Sisi baiknya, Anda masih memiliki NSObjectProtocol ... dalam hal ini ... tapi saya tahu itu bukan hal yang sama.
CommaToast