类作为 Swift 中的函数参数符合协议

在 Objective-C 中,可以将符合协议的类指定为方法参数。例如,我可以有一个只允许 UIViewController符合 UITableViewDataSource的方法:

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

我在 Swift 中找不到这样做的方法(也许现在还不可能)。您可以使用 func foo(obj: protocol<P1, P2>)指定多个协议,但是如何要求对象也是特定类的对象呢?

26988 次浏览

You can define foo as a generic function and use type constraints to require both a class and a protocol.

Swift 4

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

Swift 3 (works for Swift 4 also)

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)
}

The Swift book documentation suggests that you use type constraints with a where clause:

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

This guarantees that "inParam" is of type "SomeClass" with a condition that it also adheres to "SomeProtocol". You even have the power to specify multiple where clauses delimited by a comma:

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

Note in September 2015: This was an observation in the early days of Swift.

It seems to be impossible. Apple has this annoyance in some of their APIs as well. Here is one example from a newly introduced class in iOS 8 (as of beta 5):

UIInputViewController's textDocumentProxy property:

Defined in Objective-C as follows:

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

and in Swift:

var textDocumentProxy: NSObject! { get }

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

With Swift 3, you can do the following:

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


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

In Swift 4 you can achieve this with the new & sign:

let vc: UIViewController & UITableViewDataSource

What about this way?:

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
}

Swift 5:

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

So essentially Jeroen's answer above.

Update for Swift 5:

func yourFun<V: YourClass>(controller: V) where V: YourProtocol